@neus/sdk 1.1.5 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -1,2037 +1,2121 @@
1
- import { ApiError, ValidationError, NetworkError, ConfigurationError } from './errors.js';
2
- import { fetchSponsorGrant } from './sponsor.js';
3
- import {
4
- PORTABLE_PROOF_SIGNER_HEADER,
5
- constructVerificationMessage,
6
- validateWalletAddress,
7
- validateUniversalAddress,
8
- signMessage,
9
- NEUS_CONSTANTS
10
- } from './utils.js';
11
-
12
- const FALLBACK_PUBLIC_VERIFIER_CATALOG = {
13
- 'ownership-basic': { supportsDirectApi: true },
14
- 'ownership-social': { supportsDirectApi: false },
15
- 'ownership-pseudonym': { supportsDirectApi: true },
16
- 'ownership-dns-txt': { supportsDirectApi: true },
17
- 'ownership-org-oauth': { supportsDirectApi: false },
18
- 'contract-ownership': { supportsDirectApi: true },
19
- 'proof-of-human': { supportsDirectApi: false },
20
- 'nft-ownership': { supportsDirectApi: true },
21
- 'token-holding': { supportsDirectApi: true },
22
- 'wallet-risk': { supportsDirectApi: true },
23
- 'wallet-link': { supportsDirectApi: true },
24
- 'ai-content-moderation': { supportsDirectApi: true },
25
- 'agent-identity': { supportsDirectApi: true },
26
- 'agent-delegation': { supportsDirectApi: true }
27
- };
28
-
29
- const EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
30
- const WALLET_LINK_RELATIONSHIP_TYPES = new Set(['primary', 'personal', 'org', 'affiliate', 'agent', 'linked']);
31
-
32
- function normalizeWalletLinkRelationshipType(value) {
33
- const normalized = String(value || '').trim().toLowerCase();
34
- return WALLET_LINK_RELATIONSHIP_TYPES.has(normalized) ? normalized : 'linked';
35
- }
36
-
37
- /**
38
- * @param {unknown} raw
39
- * @returns {string | null} Non-empty trimmed string, or null if the provider value is not a valid string identity.
40
- */
41
- function normalizeBrowserSignerString(raw) {
42
- if (raw === null || raw === undefined) {
43
- return null;
44
- }
45
- if (typeof raw === 'string') {
46
- const t = raw.trim();
47
- return t.length > 0 ? t : null;
48
- }
49
- return null;
50
- }
51
-
52
- /** Treats SDK placeholder signatures as absent so the protocol can use session or app-link delegation. */
53
- function isPlaceholderNeusSignature(signature) {
54
- const s = typeof signature === 'string' ? signature.trim() : '';
55
- if (!s) return true;
56
- return /^0x0+$/i.test(s);
57
- }
58
-
59
- const validateVerifierData = (verifierId, data) => {
60
- if (!data || typeof data !== 'object') {
61
- return { valid: false, error: 'Data object is required' };
62
- }
63
-
64
- switch (verifierId) {
65
- case 'ownership-basic':
66
- if (!data.owner || !validateUniversalAddress(data.owner, typeof data.chain === 'string' ? data.chain : undefined)) {
67
- return { valid: false, error: 'owner (universal wallet address) is required' };
68
- }
69
- if (data.content !== undefined && data.content !== null) {
70
- if (typeof data.content !== 'string') {
71
- return { valid: false, error: 'content must be a string when provided' };
72
- }
73
- if (data.content.length > 50000) {
74
- return { valid: false, error: 'content exceeds 50KB inline limit' };
75
- }
76
- }
77
- if (data.contentHash !== undefined && data.contentHash !== null) {
78
- if (typeof data.contentHash !== 'string' || !/^0x[a-fA-F0-9]{64}$/.test(data.contentHash)) {
79
- return { valid: false, error: 'contentHash must be a 32-byte hex string (0x + 64 hex chars) when provided' };
80
- }
81
- }
82
- if (data.contentType !== undefined && data.contentType !== null) {
83
- if (typeof data.contentType !== 'string' || data.contentType.length > 100) {
84
- return { valid: false, error: 'contentType must be a string (max 100 chars) when provided' };
85
- }
86
- const base = String(data.contentType).split(';')[0].trim().toLowerCase();
87
- if (!base || base.includes(' ') || !base.includes('/')) {
88
- return { valid: false, error: 'contentType must be a valid MIME type when provided' };
89
- }
90
- }
91
- if (data.provenance !== undefined && data.provenance !== null) {
92
- if (!data.provenance || typeof data.provenance !== 'object' || Array.isArray(data.provenance)) {
93
- return { valid: false, error: 'provenance must be an object when provided' };
94
- }
95
- const dk = data.provenance.declaredKind;
96
- if (dk !== undefined && dk !== null) {
97
- const allowed = ['human', 'ai', 'mixed', 'unknown'];
98
- if (typeof dk !== 'string' || !allowed.includes(dk)) {
99
- return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(', ')}` };
100
- }
101
- }
102
- const ai = data.provenance.aiContext;
103
- if (ai !== undefined && ai !== null) {
104
- if (typeof ai !== 'object' || Array.isArray(ai)) {
105
- return { valid: false, error: 'provenance.aiContext must be an object when provided' };
106
- }
107
- if (ai.generatorType !== undefined && ai.generatorType !== null) {
108
- const allowed = ['local', 'saas', 'agent'];
109
- if (typeof ai.generatorType !== 'string' || !allowed.includes(ai.generatorType)) {
110
- return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(', ')}` };
111
- }
112
- }
113
- if (ai.provider !== undefined && ai.provider !== null) {
114
- if (typeof ai.provider !== 'string' || ai.provider.length > 64) {
115
- return { valid: false, error: 'provenance.aiContext.provider must be a string (max 64 chars) when provided' };
116
- }
117
- }
118
- if (ai.model !== undefined && ai.model !== null) {
119
- if (typeof ai.model !== 'string' || ai.model.length > 128) {
120
- return { valid: false, error: 'provenance.aiContext.model must be a string (max 128 chars) when provided' };
121
- }
122
- }
123
- if (ai.runId !== undefined && ai.runId !== null) {
124
- if (typeof ai.runId !== 'string' || ai.runId.length > 128) {
125
- return { valid: false, error: 'provenance.aiContext.runId must be a string (max 128 chars) when provided' };
126
- }
127
- }
128
- }
129
- }
130
- if (data.reference !== undefined) {
131
- if (!data.reference || typeof data.reference !== 'object') {
132
- return { valid: false, error: 'reference must be an object when provided' };
133
- }
134
- if (!data.reference.type || typeof data.reference.type !== 'string') {
135
- return { valid: false, error: 'reference.type is required when reference is provided' };
136
- }
137
- }
138
- if (!data.content && !data.contentHash) {
139
- if (!data.reference || typeof data.reference !== 'object') {
140
- return { valid: false, error: 'reference is required when neither content nor contentHash is provided' };
141
- }
142
- if (!data.reference.id || typeof data.reference.id !== 'string') {
143
- return { valid: false, error: 'reference.id is required when neither content nor contentHash is provided' };
144
- }
145
- if (!data.reference.type || typeof data.reference.type !== 'string') {
146
- return { valid: false, error: 'reference.type is required when neither content nor contentHash is provided' };
147
- }
148
- }
149
- break;
150
- case 'nft-ownership':
151
- if (
152
- !data.contractAddress ||
153
- data.tokenId === null ||
154
- data.tokenId === undefined ||
155
- typeof data.chainId !== 'number'
156
- ) {
157
- return { valid: false, error: 'contractAddress, tokenId, and chainId are required' };
158
- }
159
- if (data.tokenType !== undefined && data.tokenType !== null) {
160
- const tt = String(data.tokenType).toLowerCase();
161
- if (tt !== 'erc721' && tt !== 'erc1155') {
162
- return { valid: false, error: 'tokenType must be one of: erc721, erc1155' };
163
- }
164
- }
165
- if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
166
- return { valid: false, error: 'blockNumber must be an integer when provided' };
167
- }
168
- if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
169
- return { valid: false, error: 'Invalid ownerAddress' };
170
- }
171
- if (!validateWalletAddress(data.contractAddress)) {
172
- return { valid: false, error: 'Invalid contractAddress' };
173
- }
174
- break;
175
- case 'token-holding':
176
- if (
177
- !data.contractAddress ||
178
- data.minBalance === null ||
179
- data.minBalance === undefined ||
180
- typeof data.chainId !== 'number'
181
- ) {
182
- return { valid: false, error: 'contractAddress, minBalance, and chainId are required' };
183
- }
184
- if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
185
- return { valid: false, error: 'blockNumber must be an integer when provided' };
186
- }
187
- if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
188
- return { valid: false, error: 'Invalid ownerAddress' };
189
- }
190
- if (!validateWalletAddress(data.contractAddress)) {
191
- return { valid: false, error: 'Invalid contractAddress' };
192
- }
193
- break;
194
- case 'ownership-dns-txt':
195
- if (!data.domain || typeof data.domain !== 'string') {
196
- return { valid: false, error: 'domain is required' };
197
- }
198
- if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
199
- return { valid: false, error: 'Invalid walletAddress' };
200
- }
201
- break;
202
- case 'wallet-link':
203
- if (!data.primaryWalletAddress || !validateUniversalAddress(data.primaryWalletAddress, data.chain)) {
204
- return { valid: false, error: 'primaryWalletAddress is required' };
205
- }
206
- if (!data.secondaryWalletAddress || !validateUniversalAddress(data.secondaryWalletAddress, data.chain)) {
207
- return { valid: false, error: 'secondaryWalletAddress is required' };
208
- }
209
- if (!data.signature || typeof data.signature !== 'string') {
210
- return { valid: false, error: 'signature is required (signed by secondary wallet)' };
211
- }
212
- if (typeof data.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
213
- return { valid: false, error: 'chain is required (namespace:reference)' };
214
- }
215
- if (typeof data.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
216
- return { valid: false, error: 'signatureMethod is required' };
217
- }
218
- if (typeof data.signedTimestamp !== 'number') {
219
- return { valid: false, error: 'signedTimestamp is required' };
220
- }
221
- break;
222
- case 'contract-ownership':
223
- if (!data.contractAddress || !validateWalletAddress(data.contractAddress)) {
224
- return { valid: false, error: 'contractAddress is required' };
225
- }
226
- if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
227
- return { valid: false, error: 'Invalid walletAddress' };
228
- }
229
- if (typeof data.chainId !== 'number') {
230
- return { valid: false, error: 'chainId is required' };
231
- }
232
- break;
233
- case 'agent-identity':
234
- if (!data.agentId || typeof data.agentId !== 'string' || data.agentId.length < 1 || data.agentId.length > 128) {
235
- return { valid: false, error: 'agentId is required (1-128 chars)' };
236
- }
237
- if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
238
- return { valid: false, error: 'agentWallet is required' };
239
- }
240
- if (data.agentType && !['ai', 'bot', 'service', 'automation', 'agent'].includes(data.agentType)) {
241
- return { valid: false, error: 'agentType must be one of: ai, bot, service, automation, agent' };
242
- }
243
- if (data.defaultRuntime && typeof data.defaultRuntime === 'object') {
244
- if (data.defaultRuntime.provider && typeof data.defaultRuntime.provider === 'string' && data.defaultRuntime.provider.length > 64) {
245
- return { valid: false, error: 'defaultRuntime.provider must be 64 chars or less' };
246
- }
247
- if (data.defaultRuntime.model && typeof data.defaultRuntime.model === 'string' && data.defaultRuntime.model.length > 128) {
248
- return { valid: false, error: 'defaultRuntime.model must be 128 chars or less' };
249
- }
250
- if (data.defaultRuntime.mode && typeof data.defaultRuntime.mode === 'string' && data.defaultRuntime.mode.length > 64) {
251
- return { valid: false, error: 'defaultRuntime.mode must be 64 chars or less' };
252
- }
253
- }
254
- break;
255
- case 'agent-delegation':
256
- if (!data.controllerWallet || !validateWalletAddress(data.controllerWallet)) {
257
- return { valid: false, error: 'controllerWallet is required' };
258
- }
259
- if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
260
- return { valid: false, error: 'agentWallet is required' };
261
- }
262
- if (data.scope && (typeof data.scope !== 'string' || data.scope.length > 128)) {
263
- return { valid: false, error: 'scope must be a string (max 128 chars)' };
264
- }
265
- if (data.expiresAt && (typeof data.expiresAt !== 'number' || data.expiresAt < Date.now())) {
266
- return { valid: false, error: 'expiresAt must be a future timestamp' };
267
- }
268
- if (data.model && typeof data.model === 'string' && data.model.length > 128) {
269
- return { valid: false, error: 'model must be 128 chars or less' };
270
- }
271
- if (data.provider && typeof data.provider === 'string' && data.provider.length > 64) {
272
- return { valid: false, error: 'provider must be 64 chars or less' };
273
- }
274
- if (data.allowedActions && Array.isArray(data.allowedActions) && data.allowedActions.length > 32) {
275
- return { valid: false, error: 'allowedActions must have 32 items or less' };
276
- }
277
- if (data.deniedActions && Array.isArray(data.deniedActions) && data.deniedActions.length > 32) {
278
- return { valid: false, error: 'deniedActions must have 32 items or less' };
279
- }
280
- break;
281
- case 'ai-content-moderation':
282
- if (!data.content || typeof data.content !== 'string') {
283
- return { valid: false, error: 'content is required' };
284
- }
285
- if (!data.contentType || typeof data.contentType !== 'string') {
286
- return { valid: false, error: 'contentType (MIME type) is required' };
287
- }
288
- {
289
- const contentType = String(data.contentType).split(';')[0].trim().toLowerCase();
290
- const validTypes = [
291
- 'image/jpeg',
292
- 'image/png',
293
- 'image/webp',
294
- 'image/gif',
295
- 'text/plain',
296
- 'text/markdown',
297
- 'text/x-markdown',
298
- 'application/json',
299
- 'application/xml'
300
- ];
301
- if (!validTypes.includes(contentType)) {
302
- return { valid: false, error: `contentType must be one of: ${validTypes.join(', ')}` };
303
- }
304
- const isTextual = contentType.startsWith('text/') || contentType.includes('markdown');
305
- if (isTextual) {
306
- try {
307
- const maxBytes = 50 * 1024;
308
- const bytes = (typeof TextEncoder !== 'undefined')
309
- ? new TextEncoder().encode(data.content).length
310
- : String(data.content).length;
311
- if (bytes > maxBytes) {
312
- return {
313
- valid: false,
314
- error: `content exceeds ${maxBytes} bytes limit for ai-content-moderation verifier (text)`
315
- };
316
- }
317
- } catch {
318
- void 0;
319
- }
320
- }
321
- }
322
- if (data.content.length > 13653334) {
323
- return { valid: false, error: 'content exceeds 10MB limit' };
324
- }
325
- break;
326
- case 'ownership-pseudonym':
327
- if (!data.pseudonymId || typeof data.pseudonymId !== 'string') {
328
- return { valid: false, error: 'pseudonymId is required' };
329
- }
330
- if (!/^[a-z0-9._-]{3,64}$/.test(data.pseudonymId.trim().toLowerCase())) {
331
- return { valid: false, error: 'pseudonymId must be 3-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
332
- }
333
- if (data.namespace && typeof data.namespace === 'string') {
334
- if (!/^[a-z0-9._-]{1,64}$/.test(data.namespace.trim().toLowerCase())) {
335
- return { valid: false, error: 'namespace must be 1-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
336
- }
337
- }
338
- break;
339
- case 'wallet-risk':
340
- if (data.walletAddress && !validateUniversalAddress(data.walletAddress, data.chain)) {
341
- return { valid: false, error: 'Invalid walletAddress' };
342
- }
343
- break;
344
- }
345
-
346
- return { valid: true };
347
- };
348
-
349
- export class NeusClient {
350
- constructor(config = {}) {
351
- this.config = {
352
- timeout: 30000,
353
- enableLogging: false,
354
- ...config
355
- };
356
-
357
- this.baseUrl = this.config.apiUrl || 'https://api.neus.network';
358
- try {
359
- const url = new URL(this.baseUrl);
360
- if (url.hostname.endsWith('neus.network') && url.protocol === 'http:') {
361
- url.protocol = 'https:';
362
- }
363
- this.baseUrl = url.toString().replace(/\/$/, '');
364
- } catch {
365
- void 0;
366
- }
367
- this.config.apiUrl = this.baseUrl;
368
-
369
- this.defaultHeaders = {
370
- 'Content-Type': 'application/json',
371
- 'Accept': 'application/json',
372
- 'X-Neus-Sdk': 'js'
373
- };
374
-
375
- if (typeof this.config.apiKey === 'string' && this.config.apiKey.trim().length > 0) {
376
- this.defaultHeaders['Authorization'] = `Bearer ${this.config.apiKey.trim()}`;
377
- }
378
- if (typeof this.config.appId === 'string' && this.config.appId.trim().length > 0) {
379
- this.defaultHeaders['X-Neus-App'] = this.config.appId.trim();
380
- }
381
- if (typeof this.config.paymentSignature === 'string' && this.config.paymentSignature.trim().length > 0) {
382
- this.defaultHeaders['PAYMENT-SIGNATURE'] = this.config.paymentSignature.trim();
383
- }
384
- if (this.config.extraHeaders && typeof this.config.extraHeaders === 'object') {
385
- for (const [k, v] of Object.entries(this.config.extraHeaders)) {
386
- if (!k || v === undefined || v === null) continue;
387
- const key = String(k).trim();
388
- const value = String(v).trim();
389
- if (!key || !value) continue;
390
- this.defaultHeaders[key] = value;
391
- }
392
- }
393
- try {
394
- if (typeof window !== 'undefined' && window.location && window.location.origin) {
395
- this.defaultHeaders['X-Client-Origin'] = window.location.origin;
396
- }
397
- } catch {
398
- void 0;
399
- }
400
-
401
- /** @type {{ token: string, expMs: number, key: string } | null} */
402
- this._sponsorGrantCache = null;
403
- }
404
-
405
- _getBillingWallet() {
406
- const raw =
407
- this.config.billingWallet ||
408
- this.config.sponsorOrgWallet ||
409
- this.config.orgWallet ||
410
- null;
411
- if (typeof raw !== 'string') return null;
412
- const trimmed = raw.trim().toLowerCase();
413
- return /^0x[a-f0-9]{40}$/.test(trimmed) ? trimmed : null;
414
- }
415
-
416
- _resolveIntegratorOrigin() {
417
- if (typeof this.config.appOrigin === 'string' && this.config.appOrigin.trim()) {
418
- return this.config.appOrigin.trim();
419
- }
420
- try {
421
- if (typeof window !== 'undefined' && window.location?.origin) {
422
- return window.location.origin;
423
- }
424
- } catch {
425
- void 0;
426
- }
427
- return null;
428
- }
429
-
430
- async _resolveSponsorGrantHeaders(verifierIds = []) {
431
- const appId = typeof this.config.appId === 'string' ? this.config.appId.trim() : '';
432
- const orgWallet = this._getBillingWallet();
433
- if (!appId || !orgWallet) {
434
- return {};
435
- }
436
-
437
- const normalizedVerifierIds = Array.isArray(verifierIds)
438
- ? verifierIds.map((v) => String(v || '').trim()).filter(Boolean).slice(0, 25)
439
- : [];
440
- const cacheKey = `${appId}:${orgWallet}:${normalizedVerifierIds.join(',')}`;
441
- const now = Date.now();
442
- if (
443
- this._sponsorGrantCache &&
444
- this._sponsorGrantCache.key === cacheKey &&
445
- this._sponsorGrantCache.expMs > now + 30_000
446
- ) {
447
- return { 'X-Sponsor-Grant': this._sponsorGrantCache.token };
448
- }
449
-
450
- const origin = this._resolveIntegratorOrigin();
451
- const grant = await fetchSponsorGrant({
452
- apiUrl: this.baseUrl,
453
- appId,
454
- orgWallet,
455
- verifierIds: normalizedVerifierIds,
456
- origin
457
- });
458
-
459
- const expSeconds = Number(grant?.exp);
460
- const expMs = Number.isFinite(expSeconds) && expSeconds > 0
461
- ? expSeconds * 1000
462
- : now + 15 * 60 * 1000;
463
-
464
- this._sponsorGrantCache = {
465
- key: cacheKey,
466
- token: grant.sponsorGrant,
467
- expMs
468
- };
469
-
470
- return { 'X-Sponsor-Grant': grant.sponsorGrant };
471
- }
472
-
473
- _getHubChainId() {
474
- const configured = Number(this.config?.hubChainId);
475
- if (Number.isFinite(configured) && configured > 0) return Math.floor(configured);
476
- return NEUS_CONSTANTS.HUB_CHAIN_ID;
477
- }
478
-
479
- _normalizeIdentity(value) {
480
- let raw = String(value || '').trim();
481
- if (!raw) return '';
482
- const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
483
- if (didMatch && didMatch[3]) {
484
- raw = String(didMatch[3]).trim();
485
- }
486
- return EVM_ADDRESS_RE.test(raw) ? raw.toLowerCase() : raw;
487
- }
488
-
489
- _inferChainForAddress(address, explicitChain) {
490
- if (typeof explicitChain === 'string' && explicitChain.includes(':')) return explicitChain.trim();
491
- const raw = String(address || '').trim();
492
- const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
493
- if (didMatch && didMatch[1] && didMatch[2]) {
494
- return `${didMatch[1]}:${didMatch[2]}`;
495
- }
496
- if (EVM_ADDRESS_RE.test(raw)) {
497
- return `eip155:${this._getHubChainId()}`;
498
- }
499
- return 'solana:mainnet';
500
- }
501
-
502
- async _resolveWalletSigner(wallet) {
503
- if (!wallet) {
504
- throw new ConfigurationError('No wallet provider available');
505
- }
506
-
507
- if (wallet.address !== null && wallet.address !== undefined) {
508
- const s = normalizeBrowserSignerString(
509
- typeof wallet.address === 'string' ? wallet.address : null
510
- );
511
- if (s) {
512
- return { signerWalletAddress: s, provider: wallet };
513
- }
514
- }
515
- if (wallet.publicKey && typeof wallet.publicKey.toBase58 === 'function') {
516
- const b58 = wallet.publicKey.toBase58();
517
- const s = normalizeBrowserSignerString(typeof b58 === 'string' ? b58 : null);
518
- if (s) {
519
- return { signerWalletAddress: s, provider: wallet };
520
- }
521
- }
522
- if (typeof wallet.getAddress === 'function') {
523
- const got = await wallet.getAddress().catch(() => null);
524
- const s = normalizeBrowserSignerString(
525
- got === null || got === undefined ? null : (typeof got === 'string' ? got : null)
526
- );
527
- if (s) {
528
- return { signerWalletAddress: s, provider: wallet };
529
- }
530
- }
531
- if (wallet.selectedAddress || wallet.request) {
532
- const provider = wallet;
533
- if (wallet.selectedAddress) {
534
- const s = normalizeBrowserSignerString(
535
- typeof wallet.selectedAddress === 'string' ? wallet.selectedAddress : null
536
- );
537
- if (s) {
538
- return { signerWalletAddress: s, provider };
539
- }
540
- }
541
- const accounts = await provider.request({ method: 'eth_accounts' });
542
- if (!accounts || accounts.length === 0) {
543
- throw new ConfigurationError('No wallet accounts available');
544
- }
545
- const s = normalizeBrowserSignerString(accounts[0]);
546
- if (!s) {
547
- throw new ConfigurationError('No wallet accounts available');
548
- }
549
- return { signerWalletAddress: s, provider };
550
- }
551
-
552
- throw new ConfigurationError('Invalid wallet provider');
553
- }
554
-
555
- _getDefaultBrowserWallet() {
556
- if (typeof window === 'undefined') return null;
557
- // Legacy convenience fallback only. Non-EVM wallets must be passed explicitly
558
- // with CAIP-2 chain context so the SDK does not route them through EVM RPC.
559
- return window.ethereum || null;
560
- }
561
-
562
- async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
563
- const providerWallet = wallet || this._getDefaultBrowserWallet();
564
- const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
565
- if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
566
- throw new ConfigurationError('No wallet accounts available');
567
- }
568
-
569
- const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
570
- const normalizedAddress = this._normalizeIdentity(address);
571
- if (!normalizedSigner || normalizedSigner !== normalizedAddress) {
572
- throw new ValidationError('wallet must match address when includePrivate=true');
573
- }
574
-
575
- const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
576
- const resolvedChain = this._inferChainForAddress(normalizedSigner, chain);
577
- const resolvedSignatureMethod = (typeof signatureMethod === 'string' && signatureMethod.trim())
578
- ? signatureMethod.trim()
579
- : (signerIsEvm ? 'eip191' : 'ed25519');
580
-
581
- const signedTimestamp = Date.now();
582
- const message = constructVerificationMessage({
583
- walletAddress: signerWalletAddress,
584
- signedTimestamp,
585
- data: { action: 'gate_check_private_proofs', walletAddress: normalizedAddress },
586
- verifierIds: ['ownership-basic'],
587
- ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain: resolvedChain })
588
- });
589
-
590
- let signature;
591
- try {
592
- signature = await signMessage({
593
- provider,
594
- message,
595
- walletAddress: signerWalletAddress,
596
- ...(signerIsEvm ? {} : { chain: resolvedChain })
597
- });
598
- } catch (error) {
599
- if (error.code === 4001) {
600
- throw new ValidationError('User rejected signature request');
601
- }
602
- throw new ValidationError(`Failed to sign message: ${error.message}`);
603
- }
604
-
605
- return {
606
- walletAddress: signerWalletAddress,
607
- signature,
608
- signedTimestamp,
609
- ...(signerIsEvm ? {} : { chain: resolvedChain, signatureMethod: resolvedSignatureMethod })
610
- };
611
- }
612
-
613
- async createGatePrivateAuth(params = {}) {
614
- const address = (params.address || '').toString();
615
- if (!validateUniversalAddress(address, params.chain)) {
616
- throw new ValidationError('Valid address is required');
617
- }
618
- return this._buildPrivateGateAuth({
619
- address,
620
- wallet: params.wallet,
621
- chain: params.chain,
622
- signatureMethod: params.signatureMethod
623
- });
624
- }
625
-
626
- async createWalletLinkData(params = {}) {
627
- const normalizedPrimary = this._normalizeIdentity(params.primaryWalletAddress);
628
- const normalizedSecondary = this._normalizeIdentity(params.secondaryWalletAddress);
629
-
630
- if (!EVM_ADDRESS_RE.test(normalizedPrimary)) {
631
- throw new ValidationError('wallet-link requires a valid primaryWalletAddress');
632
- }
633
- if (!EVM_ADDRESS_RE.test(normalizedSecondary)) {
634
- throw new ValidationError('wallet-link requires a valid secondaryWalletAddress');
635
- }
636
- if (normalizedPrimary === normalizedSecondary) {
637
- throw new ValidationError('wallet-link secondaryWalletAddress must differ from primaryWalletAddress');
638
- }
639
-
640
- const providerWallet = params.wallet || this._getDefaultBrowserWallet();
641
- const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
642
- const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
643
- if (!EVM_ADDRESS_RE.test(normalizedSigner)) {
644
- throw new ValidationError('wallet-link requires an EVM wallet/provider for the secondary wallet');
645
- }
646
- if (normalizedSigner !== normalizedSecondary) {
647
- throw new ValidationError('wallet-link wallet/provider must be connected to secondaryWalletAddress');
648
- }
649
-
650
- const resolvedChain = (() => {
651
- const raw = String(params.chain || '').trim();
652
- if (!raw) return `eip155:${this._getHubChainId()}`;
653
- if (!/^eip155:\d+$/.test(raw)) {
654
- throw new ValidationError('wallet-link requires chain in the form eip155:<chainId>');
655
- }
656
- return raw;
657
- })();
658
- const signedTimestamp = Number.isFinite(Number(params.signedTimestamp)) && Number(params.signedTimestamp) > 0
659
- ? Number(params.signedTimestamp)
660
- : Date.now();
661
- const linkData = {
662
- primaryAccountId: `${resolvedChain}:${normalizedPrimary}`,
663
- secondaryAccountId: `${resolvedChain}:${normalizedSecondary}`,
664
- primaryWalletAddress: normalizedPrimary,
665
- secondaryWalletAddress: normalizedSecondary
666
- };
667
- const message = constructVerificationMessage({
668
- walletAddress: normalizedSecondary,
669
- signedTimestamp,
670
- data: linkData,
671
- verifierIds: ['wallet-link'],
672
- chain: resolvedChain
673
- });
674
-
675
- let signature;
676
- try {
677
- signature = await signMessage({
678
- provider,
679
- message,
680
- walletAddress: signerWalletAddress
681
- });
682
- } catch (error) {
683
- if (error?.code === 4001) {
684
- throw new ValidationError('User rejected wallet-link signature request');
685
- }
686
- throw new ValidationError(`Failed to sign wallet-link message: ${error?.message || String(error)}`);
687
- }
688
-
689
- const label = String(params.label || '').trim().slice(0, 64);
690
- return {
691
- primaryWalletAddress: normalizedPrimary,
692
- secondaryWalletAddress: normalizedSecondary,
693
- signature,
694
- chain: resolvedChain,
695
- signatureMethod: 'eip191',
696
- signedTimestamp,
697
- relationshipType: normalizeWalletLinkRelationshipType(params.relationshipType),
698
- ...(label ? { label } : {})
699
- };
700
- }
701
-
702
- async verifyFromApp(params) {
703
- if (!params || typeof params !== 'object') {
704
- throw new ValidationError('verifyFromApp requires a params object');
705
- }
706
-
707
- const {
708
- user,
709
- verifier = 'ownership-basic',
710
- content,
711
- data: explicitData = null,
712
- options = {}
713
- } = params;
714
-
715
- if (!user || typeof user !== 'object') {
716
- throw new ValidationError('verifyFromApp requires user object');
717
- }
718
-
719
- const walletAddress =
720
- user.walletAddress ||
721
- user.address ||
722
- user.identity;
723
- if (!walletAddress || typeof walletAddress !== 'string') {
724
- throw new ValidationError('verifyFromApp requires user.walletAddress');
725
- }
726
-
727
- const delegationQHash =
728
- this.config.appLinkQHash ||
729
- (typeof process !== 'undefined' && process.env && process.env.NEUS_APP_LINK_QHASH) ||
730
- null;
731
-
732
- let data;
733
- if (explicitData && typeof explicitData === 'object') {
734
- data = { owner: walletAddress, ...explicitData };
735
- } else if (content && typeof content === 'object') {
736
- data = {
737
- owner: walletAddress,
738
- content: JSON.stringify(content),
739
- contentType: typeof content.contentType === 'string' ? content.contentType : 'application/json',
740
- reference: content.reference || { type: 'other' }
741
- };
742
- } else if (typeof content === 'string') {
743
- data = {
744
- owner: walletAddress,
745
- content,
746
- reference: { type: 'other' }
747
- };
748
- } else {
749
- data = { owner: walletAddress, reference: { type: 'other' } };
750
- }
751
-
752
- const signedTimestamp = Date.now();
753
- const verifierIds = [verifier];
754
-
755
- return this.verify({
756
- verifierIds,
757
- data,
758
- walletAddress,
759
- signedTimestamp,
760
- ...(delegationQHash ? { delegationQHash } : {}),
761
- options
762
- });
763
- }
764
-
765
- async verify(params) {
766
- if (
767
- (isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) &&
768
- (params?.verifier || params?.content || params?.data)
769
- ) {
770
- const { content, verifier = 'ownership-basic', data = null, wallet = null, options = {} } = params;
771
-
772
- if (verifier === 'ownership-basic' && !data && (!content || typeof content !== 'string')) {
773
- throw new ValidationError('content is required and must be a string (or use data param with owner + reference)');
774
- }
775
-
776
- let verifierCatalog = FALLBACK_PUBLIC_VERIFIER_CATALOG;
777
- try {
778
- const discovered = await this.getVerifierCatalog();
779
- if (discovered && discovered.metadata && Object.keys(discovered.metadata).length > 0) {
780
- verifierCatalog = discovered.metadata;
781
- }
782
- } catch {
783
- void 0;
784
- }
785
- const validVerifiers = Object.keys(verifierCatalog);
786
- if (!validVerifiers.includes(verifier)) {
787
- throw new ValidationError(`Invalid verifier '${verifier}'. Must be one of: ${validVerifiers.join(', ')}.`);
788
- }
789
-
790
- if (verifierCatalog?.[verifier]?.supportsDirectApi === false) {
791
- const hostedCheckoutUrl = options?.hostedCheckoutUrl || 'https://neus.network/verify';
792
- throw new ValidationError(
793
- `${verifier} requires hosted interactive checkout. Use VerifyGate or redirect to ${hostedCheckoutUrl}.`
794
- );
795
- }
796
-
797
- const requiresDataParam = [
798
- 'ownership-dns-txt',
799
- 'wallet-link',
800
- 'contract-ownership',
801
- 'ownership-pseudonym',
802
- 'wallet-risk',
803
- 'agent-identity',
804
- 'agent-delegation',
805
- 'ai-content-moderation'
806
- ];
807
- if (requiresDataParam.includes(verifier)) {
808
- if (!data || typeof data !== 'object') {
809
- throw new ValidationError(`${verifier} requires explicit data parameter. Cannot use auto-path.`);
810
- }
811
- }
812
-
813
- let walletAddress, provider;
814
- if (wallet) {
815
- walletAddress =
816
- wallet.address ||
817
- wallet.selectedAddress ||
818
- wallet.walletAddress ||
819
- (typeof wallet.getAddress === 'function' ? await wallet.getAddress().catch(() => null) : null);
820
- provider = wallet.provider || wallet;
821
- if (!walletAddress && provider && typeof provider.request === 'function') {
822
- let accounts = await provider.request({ method: 'eth_accounts' });
823
- walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
824
- if (!walletAddress) {
825
- await provider.request({ method: 'eth_requestAccounts' });
826
- accounts = await provider.request({ method: 'eth_accounts' });
827
- walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
828
- }
829
- }
830
- } else {
831
- if (typeof window === 'undefined' || !window.ethereum) {
832
- throw new ConfigurationError('No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.');
833
- }
834
- await window.ethereum.request({ method: 'eth_requestAccounts' });
835
- provider = window.ethereum;
836
- const accounts = await provider.request({ method: 'eth_accounts' });
837
- walletAddress = accounts[0];
838
- }
839
-
840
- {
841
- const normalized = normalizeBrowserSignerString(
842
- typeof walletAddress === 'string' ? walletAddress : null
843
- );
844
- if (!normalized) {
845
- throw new ConfigurationError('No wallet accounts available. Connect a wallet to continue.');
846
- }
847
- walletAddress = normalized;
848
- }
849
-
850
- let verificationData;
851
- if (verifier === 'ownership-basic') {
852
- if (data && typeof data === 'object') {
853
- verificationData = {
854
- owner: data.owner || walletAddress,
855
- reference: data.reference,
856
- ...(data.content && { content: data.content }),
857
- ...(data.contentHash && { contentHash: data.contentHash }),
858
- ...(data.contentType && { contentType: data.contentType }),
859
- ...(data.provenance && { provenance: data.provenance })
860
- };
861
- } else {
862
- verificationData = {
863
- content: content,
864
- owner: walletAddress,
865
- reference: { type: 'other' }
866
- };
867
- }
868
- } else if (verifier === 'token-holding') {
869
- if (!data?.contractAddress) {
870
- throw new ValidationError('token-holding requires contractAddress in data parameter');
871
- }
872
- if (data?.minBalance === null || data?.minBalance === undefined) {
873
- throw new ValidationError('token-holding requires minBalance in data parameter');
874
- }
875
- if (typeof data?.chainId !== 'number') {
876
- throw new ValidationError('token-holding requires chainId (number) in data parameter');
877
- }
878
- verificationData = {
879
- ownerAddress: walletAddress,
880
- contractAddress: data.contractAddress,
881
- minBalance: data.minBalance,
882
- chainId: data.chainId
883
- };
884
- } else if (verifier === 'nft-ownership') {
885
- if (!data?.contractAddress) {
886
- throw new ValidationError('nft-ownership requires contractAddress in data parameter');
887
- }
888
- if (data?.tokenId === null || data?.tokenId === undefined) {
889
- throw new ValidationError('nft-ownership requires tokenId in data parameter');
890
- }
891
- if (typeof data?.chainId !== 'number') {
892
- throw new ValidationError('nft-ownership requires chainId (number) in data parameter');
893
- }
894
- verificationData = {
895
- ownerAddress: walletAddress,
896
- contractAddress: data.contractAddress,
897
- tokenId: data.tokenId,
898
- chainId: data.chainId,
899
- tokenType: data?.tokenType || 'erc721'
900
- };
901
- } else if (verifier === 'ownership-dns-txt') {
902
- if (!data?.domain) {
903
- throw new ValidationError('ownership-dns-txt requires domain in data parameter');
904
- }
905
- verificationData = {
906
- domain: data.domain,
907
- walletAddress: walletAddress
908
- };
909
- } else if (verifier === 'wallet-link') {
910
- if (!data?.secondaryWalletAddress) {
911
- throw new ValidationError('wallet-link requires secondaryWalletAddress in data parameter');
912
- }
913
- if (!data?.signature) {
914
- throw new ValidationError('wallet-link requires signature in data parameter (signed by secondary wallet)');
915
- }
916
- if (typeof data?.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
917
- throw new ValidationError('wallet-link requires chain (namespace:reference) in data parameter');
918
- }
919
- if (typeof data?.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
920
- throw new ValidationError('wallet-link requires signatureMethod in data parameter');
921
- }
922
- verificationData = {
923
- primaryWalletAddress: walletAddress,
924
- secondaryWalletAddress: data.secondaryWalletAddress,
925
- signature: data.signature,
926
- chain: data.chain,
927
- signatureMethod: data.signatureMethod,
928
- signedTimestamp: data?.signedTimestamp || Date.now()
929
- };
930
- } else if (verifier === 'contract-ownership') {
931
- if (!data?.contractAddress) {
932
- throw new ValidationError('contract-ownership requires contractAddress in data parameter');
933
- }
934
- if (typeof data?.chainId !== 'number') {
935
- throw new ValidationError('contract-ownership requires chainId (number) in data parameter');
936
- }
937
- verificationData = {
938
- contractAddress: data.contractAddress,
939
- walletAddress: walletAddress,
940
- chainId: data.chainId,
941
- ...(data?.method && { method: data.method })
942
- };
943
- } else if (verifier === 'agent-identity') {
944
- if (!data?.agentId) {
945
- throw new ValidationError('agent-identity requires agentId in data parameter');
946
- }
947
- verificationData = {
948
- agentId: data.agentId,
949
- agentWallet: data?.agentWallet || walletAddress,
950
- ...(data?.agentChainRef && { agentChainRef: data.agentChainRef }),
951
- ...(data?.agentAccountId && { agentAccountId: data.agentAccountId }),
952
- ...(data?.agentLabel && { agentLabel: data.agentLabel }),
953
- ...(data?.agentType && { agentType: data.agentType }),
954
- ...(data?.avatar && { avatar: data.avatar }),
955
- ...(data?.description && { description: data.description }),
956
- ...(data?.defaultRuntime && { defaultRuntime: data.defaultRuntime }),
957
- ...(data?.capabilities && { capabilities: data.capabilities }),
958
- ...(data?.instructions && { instructions: data.instructions }),
959
- ...(data?.skills && { skills: data.skills }),
960
- ...(data?.services && { services: data.services })
961
- };
962
- } else if (verifier === 'agent-delegation') {
963
- if (!data?.agentWallet) {
964
- throw new ValidationError('agent-delegation requires agentWallet in data parameter');
965
- }
966
- verificationData = {
967
- controllerWallet: data?.controllerWallet || walletAddress,
968
- ...(data?.controllerChainRef && { controllerChainRef: data.controllerChainRef }),
969
- agentWallet: data.agentWallet,
970
- ...(data?.agentChainRef && { agentChainRef: data.agentChainRef }),
971
- ...(data?.controllerAccountId && { controllerAccountId: data.controllerAccountId }),
972
- ...(data?.agentAccountId && { agentAccountId: data.agentAccountId }),
973
- ...(data?.agentId && { agentId: data.agentId }),
974
- ...(data?.scope && { scope: data.scope }),
975
- ...(data?.permissions && { permissions: data.permissions }),
976
- ...(data?.maxSpend && { maxSpend: data.maxSpend }),
977
- ...(data?.allowedPaymentTypes && { allowedPaymentTypes: data.allowedPaymentTypes }),
978
- ...(data?.receiptDisclosure && { receiptDisclosure: data.receiptDisclosure }),
979
- ...(data?.expiresAt && { expiresAt: data.expiresAt }),
980
- ...(data?.instructions && { instructions: data.instructions }),
981
- ...(data?.skills && { skills: data.skills }),
982
- ...(data?.model && { model: data.model }),
983
- ...(data?.provider && { provider: data.provider }),
984
- ...(data?.runtimePolicy && { runtimePolicy: data.runtimePolicy }),
985
- ...(data?.allowedActions && { allowedActions: data.allowedActions }),
986
- ...(data?.deniedActions && { deniedActions: data.deniedActions }),
987
- ...(data?.approvalPolicy && { approvalPolicy: data.approvalPolicy })
988
- };
989
- } else if (verifier === 'ai-content-moderation') {
990
- if (!data?.content) {
991
- throw new ValidationError('ai-content-moderation requires content (base64) in data parameter');
992
- }
993
- if (!data?.contentType) {
994
- throw new ValidationError('ai-content-moderation requires contentType (MIME type) in data parameter');
995
- }
996
- verificationData = {
997
- content: data.content,
998
- contentType: data.contentType,
999
- ...(data?.provider && { provider: data.provider })
1000
- };
1001
- } else if (verifier === 'ownership-pseudonym') {
1002
- if (!data?.pseudonymId) {
1003
- throw new ValidationError('ownership-pseudonym requires pseudonymId in data parameter');
1004
- }
1005
- verificationData = {
1006
- pseudonymId: data.pseudonymId,
1007
- ...(data?.namespace && { namespace: data.namespace }),
1008
- ...(data?.displayName && { displayName: data.displayName }),
1009
- ...(data?.metadata && { metadata: data.metadata })
1010
- };
1011
- } else if (verifier === 'wallet-risk') {
1012
- verificationData = {
1013
- walletAddress: data?.walletAddress || walletAddress,
1014
- ...(data?.provider && { provider: data.provider }),
1015
- chainId: (typeof data?.chainId === 'number' && Number.isFinite(data.chainId)) ? data.chainId : 1,
1016
- ...(data?.includeDetails !== undefined && { includeDetails: data.includeDetails })
1017
- };
1018
- } else {
1019
- verificationData = data ? {
1020
- content,
1021
- owner: walletAddress,
1022
- ...data
1023
- } : {
1024
- content,
1025
- owner: walletAddress
1026
- };
1027
- }
1028
-
1029
- const signedTimestamp = Date.now();
1030
- const verifierIds = [verifier];
1031
- const message = constructVerificationMessage({
1032
- walletAddress,
1033
- signedTimestamp,
1034
- data: verificationData,
1035
- verifierIds,
1036
- chainId: this._getHubChainId()
1037
- });
1038
-
1039
- let signature;
1040
- try {
1041
- const toHexUtf8 = (s) => {
1042
- try {
1043
- const enc = new TextEncoder();
1044
- const bytes = enc.encode(s);
1045
- let hex = '0x';
1046
- for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, '0');
1047
- return hex;
1048
- } catch {
1049
- let hex = '0x';
1050
- for (let i = 0; i < s.length; i++) hex += s.charCodeAt(i).toString(16).padStart(2, '0');
1051
- return hex;
1052
- }
1053
- };
1054
-
1055
- const isBaseMiniAppWallet = (() => {
1056
- if (typeof window === 'undefined') return false;
1057
- try {
1058
- const w = window;
1059
- const mini = w.mini;
1060
- if (!mini) return false;
1061
- const miniProvider = mini.wallet || mini.provider;
1062
- if (miniProvider === provider) return true;
1063
- if (w.ethereum === provider && mini) return true;
1064
- } catch {
1065
- void 0;
1066
- }
1067
- return false;
1068
- })();
1069
-
1070
- if (isBaseMiniAppWallet) {
1071
- try {
1072
- const hexMsg = toHexUtf8(message);
1073
- signature = await provider.request({ method: 'personal_sign', params: [hexMsg, walletAddress] });
1074
- } catch (e) {
1075
- void e;
1076
- }
1077
- }
1078
-
1079
- if (!signature) {
1080
- try {
1081
- signature = await provider.request({ method: 'personal_sign', params: [message, walletAddress] });
1082
- } catch (e) {
1083
- const msg = String(e && (e.message || e.reason) || e || '').toLowerCase();
1084
- const errCode = (e && (e.code || (e.error && e.error.code))) || null;
1085
- const needsHex = /byte|bytes|invalid byte sequence|encoding|non-hex/i.test(msg);
1086
-
1087
- const unsupportedRe =
1088
- /method.*not.*supported|unsupported|not implemented|method not found|unknown method|does not support/i;
1089
- const methodUnsupported = (
1090
- unsupportedRe.test(msg) ||
1091
- errCode === -32601 ||
1092
- errCode === 4200 ||
1093
- (msg.includes('personal_sign') && msg.includes('not')) ||
1094
- (msg.includes('request method') && msg.includes('not supported'))
1095
- );
1096
-
1097
- if (methodUnsupported) {
1098
- this._log('personal_sign not supported; attempting eth_sign fallback');
1099
- try {
1100
- const enc = new TextEncoder();
1101
- const bytes = enc.encode(message);
1102
- const prefix = `\x19Ethereum Signed Message:\n${bytes.length}`;
1103
- const full = new Uint8Array(prefix.length + bytes.length);
1104
- for (let i = 0; i < prefix.length; i++) full[i] = prefix.charCodeAt(i);
1105
- full.set(bytes, prefix.length);
1106
- let payloadHex = '0x';
1107
- for (let i = 0; i < full.length; i++) payloadHex += full[i].toString(16).padStart(2, '0');
1108
- try {
1109
- if (typeof window !== 'undefined') window.__NEUS_ALLOW_ETH_SIGN__ = true;
1110
- signature = await provider.request({ method: 'eth_sign', params: [walletAddress, payloadHex], neusAllowEthSign: true });
1111
- } finally {
1112
- try { if (typeof window !== 'undefined') delete window.__NEUS_ALLOW_ETH_SIGN__; } catch { void 0; }
1113
- }
1114
- } catch (fallbackErr) {
1115
- this._log('eth_sign fallback failed', { message: fallbackErr?.message || String(fallbackErr) });
1116
- throw e;
1117
- }
1118
- } else if (needsHex) {
1119
- this._log('Retrying personal_sign with hex-encoded message');
1120
- const hexMsg = toHexUtf8(message);
1121
- signature = await provider.request({ method: 'personal_sign', params: [hexMsg, walletAddress] });
1122
- } else {
1123
- throw e;
1124
- }
1125
- }
1126
- }
1127
- } catch (error) {
1128
- if (error.code === 4001) {
1129
- throw new ValidationError('User rejected the signature request. Signature is required to create proofs.');
1130
- }
1131
- throw new ValidationError(`Failed to sign verification message: ${error.message}`);
1132
- }
1133
-
1134
- return this.verify({
1135
- verifierIds,
1136
- data: verificationData,
1137
- walletAddress,
1138
- signature,
1139
- signedTimestamp,
1140
- options
1141
- });
1142
- }
1143
-
1144
- const {
1145
- verifierIds,
1146
- data,
1147
- walletAddress,
1148
- signature: rawSignature,
1149
- signedTimestamp,
1150
- chainId,
1151
- chain,
1152
- signatureMethod,
1153
- delegationQHash,
1154
- options = {}
1155
- } = params;
1156
-
1157
- const signature = isPlaceholderNeusSignature(rawSignature) ? undefined : rawSignature;
1158
-
1159
- const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
1160
-
1161
- const normalizeVerifierId = (id) => {
1162
- if (typeof id !== 'string') return id;
1163
- const match = id.match(/^(.*)@\d+$/);
1164
- return match ? match[1] : id;
1165
- };
1166
- const normalizedVerifierIds = Array.isArray(verifierIds) ? verifierIds.map(normalizeVerifierId) : [];
1167
-
1168
- if (!normalizedVerifierIds || normalizedVerifierIds.length === 0) {
1169
- throw new ValidationError('verifierIds array is required');
1170
- }
1171
- if (!data || typeof data !== 'object') {
1172
- throw new ValidationError('data object is required');
1173
- }
1174
- if (!walletAddress || typeof walletAddress !== 'string') {
1175
- throw new ValidationError('walletAddress is required');
1176
- }
1177
- const hasAppAttribution =
1178
- typeof this.config.appId === 'string' && this.config.appId.trim().length > 0;
1179
- if (!signature && !delegationQHash && !hasAppAttribution) {
1180
- throw new ValidationError(
1181
- 'signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required'
1182
- );
1183
- }
1184
- if (!signedTimestamp || typeof signedTimestamp !== 'number') {
1185
- throw new ValidationError('signedTimestamp is required');
1186
- }
1187
- if (resolvedChainId !== null && typeof resolvedChainId !== 'number') {
1188
- throw new ValidationError('chainId must be a number');
1189
- }
1190
-
1191
- for (const verifierId of normalizedVerifierIds) {
1192
- const validation = validateVerifierData(verifierId, data);
1193
- if (!validation.valid) {
1194
- throw new ValidationError(`Validation failed for verifier '${verifierId}': ${validation.error}`);
1195
- }
1196
- }
1197
-
1198
- const optionsPayload = {
1199
- ...(options && typeof options === 'object' ? options : {}),
1200
- targetChains: Array.isArray(options?.targetChains) ? options.targetChains : [],
1201
- privacyLevel: options?.privacyLevel || 'private',
1202
- publicDisplay: options?.publicDisplay || false,
1203
- storeOriginalContent:
1204
- typeof options?.storeOriginalContent === 'boolean' ? options.storeOriginalContent : true
1205
- };
1206
- if (typeof options?.enableIpfs === 'boolean') optionsPayload.enableIpfs = options.enableIpfs;
1207
-
1208
- const requestData = {
1209
- verifierIds: normalizedVerifierIds,
1210
- data,
1211
- walletAddress,
1212
- ...(signature ? { signature } : {}),
1213
- signedTimestamp,
1214
- ...(resolvedChainId !== null && { chainId: resolvedChainId }),
1215
- ...(chain && { chain }),
1216
- ...(signatureMethod && { signatureMethod }),
1217
- ...(delegationQHash && { delegationQHash }),
1218
- options: optionsPayload
1219
- };
1220
-
1221
- const sponsorHeaders = await this._resolveSponsorGrantHeaders(normalizedVerifierIds);
1222
- const response = await this._makeRequest('POST', '/api/v1/verification', requestData, sponsorHeaders);
1223
-
1224
- if (!response.success) {
1225
- throw new ApiError(`Verification failed: ${response.error?.message || 'Unknown error'}`, response.error);
1226
- }
1227
-
1228
- return this._formatResponse(response);
1229
- }
1230
-
1231
- async getProof(qHash) {
1232
- if (!qHash || typeof qHash !== 'string') {
1233
- throw new ValidationError('qHash is required');
1234
- }
1235
- const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`);
1236
-
1237
- if (!response.success) {
1238
- throw new ApiError(`Failed to get proof: ${response.error?.message || 'Unknown error'}`, response.error);
1239
- }
1240
-
1241
- return this._formatResponse(response);
1242
- }
1243
-
1244
- async getPrivateProof(qHash, wallet = null) {
1245
- if (!qHash || typeof qHash !== 'string') {
1246
- throw new ValidationError('qHash is required');
1247
- }
1248
-
1249
- const isPreSignedAuth = wallet &&
1250
- typeof wallet === 'object' &&
1251
- typeof wallet.walletAddress === 'string' &&
1252
- typeof wallet.signature === 'string' &&
1253
- typeof wallet.signedTimestamp === 'number';
1254
- if (isPreSignedAuth) {
1255
- const auth = wallet;
1256
- const headers = {
1257
- 'x-wallet-address': String(auth.walletAddress),
1258
- 'x-signature': String(auth.signature),
1259
- 'x-signed-timestamp': String(auth.signedTimestamp),
1260
- ...(typeof auth.chain === 'string' && auth.chain.trim() ? { 'x-chain': auth.chain.trim() } : {}),
1261
- ...(typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim() ? { 'x-signature-method': auth.signatureMethod.trim() } : {})
1262
- };
1263
- const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`, null, headers);
1264
- if (!response.success) {
1265
- throw new ApiError(
1266
- `Failed to access private proof: ${response.error?.message || 'Unauthorized'}`,
1267
- response.error
1268
- );
1269
- }
1270
- return this._formatResponse(response);
1271
- }
1272
-
1273
- const providerWallet = wallet || this._getDefaultBrowserWallet();
1274
- const { signerWalletAddress: walletAddress, provider } = await this._resolveWalletSigner(providerWallet);
1275
- if (!walletAddress || typeof walletAddress !== 'string') {
1276
- throw new ConfigurationError('No wallet accounts available');
1277
- }
1278
- const signerIsEvm = EVM_ADDRESS_RE.test(this._normalizeIdentity(walletAddress));
1279
- const chain = this._inferChainForAddress(walletAddress);
1280
- const signatureMethod = signerIsEvm ? 'eip191' : 'ed25519';
1281
-
1282
- const signedTimestamp = Date.now();
1283
-
1284
- const message = constructVerificationMessage({
1285
- walletAddress,
1286
- signedTimestamp,
1287
- data: { action: 'access_private_proof', qHash: qHash },
1288
- verifierIds: ['ownership-basic'],
1289
- ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
1290
- });
1291
-
1292
- let signature;
1293
- try {
1294
- signature = await signMessage({
1295
- provider,
1296
- message,
1297
- walletAddress,
1298
- ...(signerIsEvm ? {} : { chain })
1299
- });
1300
- } catch (error) {
1301
- if (error.code === 4001) {
1302
- throw new ValidationError('User rejected signature request');
1303
- }
1304
- throw new ValidationError(`Failed to sign message: ${error.message}`);
1305
- }
1306
-
1307
- const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`, null, {
1308
- 'x-wallet-address': walletAddress,
1309
- 'x-signature': signature,
1310
- 'x-signed-timestamp': signedTimestamp.toString(),
1311
- ...(signerIsEvm ? {} : { 'x-chain': chain, 'x-signature-method': signatureMethod })
1312
- });
1313
-
1314
- if (!response.success) {
1315
- throw new ApiError(
1316
- `Failed to access private proof: ${response.error?.message || 'Unauthorized'}`,
1317
- response.error
1318
- );
1319
- }
1320
-
1321
- return this._formatResponse(response);
1322
- }
1323
-
1324
- async isHealthy() {
1325
- try {
1326
- const response = await this._makeRequest('GET', '/api/v1/health');
1327
- return response.success === true;
1328
- } catch {
1329
- return false;
1330
- }
1331
- }
1332
-
1333
- async getVerifiers() {
1334
- const catalog = await this.getVerifierCatalog();
1335
- return Array.isArray(catalog?.data) ? catalog.data : [];
1336
- }
1337
-
1338
- async getVerifierCatalog() {
1339
- const response = await this._makeRequest('GET', '/api/v1/verification/verifiers');
1340
- if (!response.success) {
1341
- throw new ApiError(`Failed to get verifiers: ${response.error?.message || 'Unknown error'}`, response.error);
1342
- }
1343
- return {
1344
- data: Array.isArray(response.data) ? response.data : [],
1345
- metadata:
1346
- response.metadata && typeof response.metadata === 'object' && !Array.isArray(response.metadata)
1347
- ? response.metadata
1348
- : {},
1349
- meta:
1350
- response.meta && typeof response.meta === 'object' && !Array.isArray(response.meta)
1351
- ? response.meta
1352
- : {}
1353
- };
1354
- }
1355
-
1356
- async pollProofStatus(qHash, options = {}) {
1357
- const {
1358
- interval = 5000,
1359
- timeout = 120000,
1360
- onProgress
1361
- } = options;
1362
-
1363
- if (!qHash || typeof qHash !== 'string') {
1364
- throw new ValidationError('qHash is required');
1365
- }
1366
-
1367
- const startTime = Date.now();
1368
- let consecutiveRateLimits = 0;
1369
-
1370
- while (Date.now() - startTime < timeout) {
1371
- try {
1372
- const status = await this.getProof(qHash);
1373
- consecutiveRateLimits = 0;
1374
-
1375
- if (onProgress && typeof onProgress === 'function') {
1376
- onProgress(status.data || status);
1377
- }
1378
-
1379
- const currentStatus = status.data?.status || status.status;
1380
- if (this._isTerminalStatus(currentStatus)) {
1381
- this._log('Verification completed', { status: currentStatus, duration: Date.now() - startTime });
1382
- return status;
1383
- }
1384
-
1385
- await new Promise(resolve => setTimeout(resolve, interval));
1386
-
1387
- } catch (error) {
1388
- this._log('Status poll error', error.message);
1389
- if (error instanceof ValidationError) {
1390
- throw error;
1391
- }
1392
-
1393
- let nextDelay = interval;
1394
- if (error instanceof ApiError && Number(error.statusCode) === 429) {
1395
- consecutiveRateLimits += 1;
1396
- const exp = Math.min(6, consecutiveRateLimits);
1397
- const base = Math.max(500, Number(interval) || 0);
1398
- const max = 30000;
1399
- const backoff = Math.min(max, base * Math.pow(2, exp));
1400
- const jitter = Math.floor(backoff * (0.5 + Math.random() * 0.5));
1401
- nextDelay = jitter;
1402
- }
1403
-
1404
- await new Promise(resolve => setTimeout(resolve, nextDelay));
1405
- }
1406
- }
1407
-
1408
- throw new NetworkError(`Polling timeout after ${timeout}ms`, 'POLLING_TIMEOUT');
1409
- }
1410
-
1411
- async detectChainId() {
1412
- if (typeof window === 'undefined' || !window.ethereum) {
1413
- throw new ConfigurationError('No Web3 wallet detected');
1414
- }
1415
-
1416
- try {
1417
- const chainId = await window.ethereum.request({ method: 'eth_chainId' });
1418
- return parseInt(chainId, 16);
1419
- } catch (error) {
1420
- throw new NetworkError(`Failed to detect chain ID: ${error.message}`);
1421
- }
1422
- }
1423
-
1424
- async revokeOwnProof(qHash, wallet) {
1425
- if (!qHash || typeof qHash !== 'string') {
1426
- throw new ValidationError('qHash is required');
1427
- }
1428
- const providerWallet = wallet || this._getDefaultBrowserWallet();
1429
- const { signerWalletAddress: address, provider } = await this._resolveWalletSigner(providerWallet);
1430
- if (!address || typeof address !== 'string') {
1431
- throw new ConfigurationError('No wallet accounts available');
1432
- }
1433
- const signerIsEvm = EVM_ADDRESS_RE.test(this._normalizeIdentity(address));
1434
- const chain = this._inferChainForAddress(address);
1435
- const signatureMethod = signerIsEvm ? 'eip191' : 'ed25519';
1436
- const signedTimestamp = Date.now();
1437
-
1438
- const message = constructVerificationMessage({
1439
- walletAddress: address,
1440
- signedTimestamp,
1441
- data: { action: 'revoke_proof', qHash: qHash },
1442
- verifierIds: ['ownership-basic'],
1443
- ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
1444
- });
1445
-
1446
- let signature;
1447
- try {
1448
- signature = await signMessage({
1449
- provider,
1450
- message,
1451
- walletAddress: address,
1452
- ...(signerIsEvm ? {} : { chain })
1453
- });
1454
- } catch (error) {
1455
- if (error.code === 4001) {
1456
- throw new ValidationError('User rejected revocation signature');
1457
- }
1458
- throw new ValidationError(`Failed to sign revocation: ${error.message}`);
1459
- }
1460
-
1461
- const res = await this._makeRequest('POST', `/api/v1/proofs/revoke-self/${qHash}`, {
1462
- walletAddress: address,
1463
- signature,
1464
- signedTimestamp,
1465
- ...(signerIsEvm ? {} : { chain, signatureMethod })
1466
- });
1467
- const json = await res.json();
1468
- if (!json.success) {
1469
- throw new ApiError(json.error?.message || 'Failed to revoke proof', json.error);
1470
- }
1471
- return true;
1472
- }
1473
-
1474
- async getProofsByWallet(walletAddress, options = {}) {
1475
- if (!walletAddress || typeof walletAddress !== 'string') {
1476
- throw new ValidationError('walletAddress is required');
1477
- }
1478
-
1479
- const id = walletAddress.trim();
1480
- const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
1481
-
1482
- const qs = [];
1483
- if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
1484
- const cursorRaw = options.cursor !== null && options.cursor !== undefined ? String(options.cursor).trim() : '';
1485
- if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
1486
- else if (options.offset) qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
1487
- if (options.qHash) qs.push(`qHash=${encodeURIComponent(options.qHash.toLowerCase())}`);
1488
-
1489
- const query = qs.length ? `?${qs.join('&')}` : '';
1490
- const response = await this._makeRequest(
1491
- 'GET',
1492
- `/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`
1493
- );
1494
-
1495
- if (!response.success) {
1496
- throw new ApiError(`Failed to get proofs: ${response.error?.message || 'Unknown error'}`, response.error);
1497
- }
1498
-
1499
- const proofs = response.data?.proofs || response.data || response.proofs || [];
1500
- return {
1501
- success: true,
1502
- proofs: Array.isArray(proofs) ? proofs : [],
1503
- totalCount: response.data?.totalCount ?? proofs.length,
1504
- hasMore: Boolean(response.data?.hasMore),
1505
- nextOffset: response.data?.nextOffset ?? null,
1506
- nextCursor:
1507
- typeof response.data?.nextCursor === 'string' && response.data.nextCursor.trim()
1508
- ? response.data.nextCursor.trim()
1509
- : null
1510
- };
1511
- }
1512
-
1513
- async getPrivateProofsByWallet(walletAddress, options = {}, wallet = null) {
1514
- if (!walletAddress || typeof walletAddress !== 'string') {
1515
- throw new ValidationError('walletAddress is required');
1516
- }
1517
-
1518
- const id = walletAddress.trim();
1519
- const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
1520
-
1521
- const requestedIdentity = this._normalizeIdentity(id);
1522
-
1523
- if (!wallet) {
1524
- const defaultWallet = this._getDefaultBrowserWallet();
1525
- if (!defaultWallet) {
1526
- throw new ConfigurationError('No wallet provider available');
1527
- }
1528
- wallet = defaultWallet;
1529
- }
1530
-
1531
- const { signerWalletAddress, provider } = await this._resolveWalletSigner(wallet);
1532
-
1533
- if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
1534
- throw new ConfigurationError('No wallet accounts available');
1535
- }
1536
-
1537
- const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
1538
- if (!normalizedSigner || normalizedSigner !== requestedIdentity) {
1539
- throw new ValidationError('wallet must match walletAddress for private proof access');
1540
- }
1541
-
1542
- const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
1543
- const chain = this._inferChainForAddress(normalizedSigner, options?.chain);
1544
- const signatureMethod = (typeof options?.signatureMethod === 'string' && options.signatureMethod.trim())
1545
- ? options.signatureMethod.trim()
1546
- : (signerIsEvm ? 'eip191' : 'ed25519');
1547
-
1548
- const signedTimestamp = Date.now();
1549
- const message = constructVerificationMessage({
1550
- walletAddress: signerWalletAddress,
1551
- signedTimestamp,
1552
- data: { action: 'access_private_proofs_by_wallet', walletAddress: normalizedSigner },
1553
- verifierIds: ['ownership-basic'],
1554
- ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
1555
- });
1556
-
1557
- let signature;
1558
- try {
1559
- signature = await signMessage({
1560
- provider,
1561
- message,
1562
- walletAddress: signerWalletAddress,
1563
- ...(signerIsEvm ? {} : { chain })
1564
- });
1565
- } catch (error) {
1566
- if (error.code === 4001) {
1567
- throw new ValidationError('User rejected signature request');
1568
- }
1569
- throw new ValidationError(`Failed to sign message: ${error.message}`);
1570
- }
1571
-
1572
- const qs = [];
1573
- if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
1574
- const cursorRaw = options.cursor !== null && options.cursor !== undefined ? String(options.cursor).trim() : '';
1575
- if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
1576
- else if (options.offset) qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
1577
- if (options.qHash) qs.push(`qHash=${encodeURIComponent(options.qHash.toLowerCase())}`);
1578
- const query = qs.length ? `?${qs.join('&')}` : '';
1579
-
1580
- const response = await this._makeRequest('GET', `/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`, null, {
1581
- 'x-wallet-address': signerWalletAddress,
1582
- 'x-signature': signature,
1583
- 'x-signed-timestamp': signedTimestamp.toString(),
1584
- ...(signerIsEvm ? {} : { 'x-chain': chain, 'x-signature-method': signatureMethod })
1585
- });
1586
-
1587
- if (!response.success) {
1588
- throw new ApiError(`Failed to get proofs: ${response.error?.message || 'Unauthorized'}`, response.error);
1589
- }
1590
-
1591
- const proofs = response.data?.proofs || response.data || response.proofs || [];
1592
- return {
1593
- success: true,
1594
- proofs: Array.isArray(proofs) ? proofs : [],
1595
- totalCount: response.data?.totalCount ?? proofs.length,
1596
- hasMore: Boolean(response.data?.hasMore),
1597
- nextOffset: response.data?.nextOffset ?? null,
1598
- nextCursor:
1599
- typeof response.data?.nextCursor === 'string' && response.data.nextCursor.trim()
1600
- ? response.data.nextCursor.trim()
1601
- : null
1602
- };
1603
- }
1604
-
1605
- async gateCheck(params = {}) {
1606
- const address = (params.address || '').toString();
1607
- if (!validateUniversalAddress(address, params.chain)) {
1608
- throw new ValidationError('Valid address is required');
1609
- }
1610
-
1611
- const gateIdParam = typeof params.gateId === 'string' ? params.gateId.trim() : '';
1612
- const verifierIds = gateIdParam ? undefined : params.verifierIds;
1613
-
1614
- const qs = new URLSearchParams();
1615
- qs.set('address', address);
1616
-
1617
- const setIfPresent = (key, value) => {
1618
- if (value === undefined || value === null) return;
1619
- const str = typeof value === 'string' ? value : String(value);
1620
- if (str.length === 0) return;
1621
- qs.set(key, str);
1622
- };
1623
-
1624
- const setBoolIfPresent = (key, value) => {
1625
- if (value === undefined || value === null) return;
1626
- qs.set(key, value ? 'true' : 'false');
1627
- };
1628
-
1629
- const setCsvIfPresent = (key, value) => {
1630
- if (value === undefined || value === null) return;
1631
- if (Array.isArray(value)) {
1632
- const items = value.map(v => String(v).trim()).filter(Boolean);
1633
- if (items.length) qs.set(key, items.join(','));
1634
- return;
1635
- }
1636
- setIfPresent(key, value);
1637
- };
1638
-
1639
- setIfPresent('gateId', gateIdParam);
1640
- setCsvIfPresent('verifierIds', verifierIds);
1641
- setBoolIfPresent('requireAll', params.requireAll);
1642
- setIfPresent('minCount', params.minCount);
1643
- setIfPresent('sinceDays', params.sinceDays);
1644
- setIfPresent('since', params.since);
1645
- setIfPresent('limit', params.limit);
1646
- setBoolIfPresent('includePrivate', params.includePrivate);
1647
- setBoolIfPresent('includeQHashes', params.includeQHashes);
1648
-
1649
- setIfPresent('referenceType', params.referenceType);
1650
- setIfPresent('referenceId', params.referenceId);
1651
- setIfPresent('tag', params.tag);
1652
- setCsvIfPresent('tags', params.tags);
1653
- setIfPresent('contentType', params.contentType);
1654
- setIfPresent('content', params.content);
1655
- setIfPresent('contentHash', params.contentHash);
1656
-
1657
- setIfPresent('contractAddress', params.contractAddress);
1658
- setIfPresent('tokenId', params.tokenId);
1659
- setIfPresent('chainId', params.chainId);
1660
- setIfPresent('domain', params.domain);
1661
- setIfPresent('minBalance', params.minBalance);
1662
-
1663
- setIfPresent('provider', params.provider);
1664
- setIfPresent('handle', params.handle);
1665
- setIfPresent('namespace', params.namespace);
1666
- setIfPresent('ownerAddress', params.ownerAddress);
1667
- setIfPresent('riskLevel', params.riskLevel);
1668
- setBoolIfPresent('sanctioned', params.sanctioned);
1669
- setBoolIfPresent('poisoned', params.poisoned);
1670
- setIfPresent('primaryWalletAddress', params.primaryWalletAddress);
1671
- setIfPresent('secondaryWalletAddress', params.secondaryWalletAddress);
1672
- setIfPresent('verificationMethod', params.verificationMethod);
1673
- setIfPresent('neusPersonhoodId', params.neusPersonhoodId);
1674
-
1675
- let headersOverride = null;
1676
- if (params.includePrivate === true) {
1677
- const provided = params.privateAuth && typeof params.privateAuth === 'object' ? params.privateAuth : null;
1678
- let auth = provided;
1679
- if (!auth) {
1680
- const walletCandidate = params.wallet || this._getDefaultBrowserWallet();
1681
- if (walletCandidate) {
1682
- auth = await this._buildPrivateGateAuth({
1683
- address,
1684
- wallet: walletCandidate,
1685
- chain: params.chain,
1686
- signatureMethod: params.signatureMethod
1687
- });
1688
- }
1689
- }
1690
- if (auth) {
1691
- const normalizedAuthWallet = this._normalizeIdentity(auth.walletAddress);
1692
- const normalizedAddress = this._normalizeIdentity(address);
1693
- if (!normalizedAuthWallet || normalizedAuthWallet !== normalizedAddress) {
1694
- throw new ValidationError('privateAuth.walletAddress must match address when includePrivate=true');
1695
- }
1696
- const authChain = (typeof auth.chain === 'string' && auth.chain.includes(':')) ? auth.chain.trim() : null;
1697
- const authSignatureMethod = (typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim())
1698
- ? auth.signatureMethod.trim()
1699
- : null;
1700
- headersOverride = {
1701
- 'x-wallet-address': String(auth.walletAddress),
1702
- 'x-signature': String(auth.signature),
1703
- 'x-signed-timestamp': String(auth.signedTimestamp),
1704
- ...(authChain ? { 'x-chain': authChain } : {}),
1705
- ...(authSignatureMethod ? { 'x-signature-method': authSignatureMethod } : {})
1706
- };
1707
- }
1708
- }
1709
-
1710
- let mergedHeaders = headersOverride;
1711
- if (!mergedHeaders && !gateIdParam) {
1712
- try {
1713
- const sponsorHeaders = await this._resolveSponsorGrantHeaders(
1714
- Array.isArray(verifierIds)
1715
- ? verifierIds
1716
- : (verifierIds ? [verifierIds] : [])
1717
- );
1718
- if (sponsorHeaders && Object.keys(sponsorHeaders).length > 0) {
1719
- mergedHeaders = sponsorHeaders;
1720
- }
1721
- } catch (error) {
1722
- this._log('Sponsor grant unavailable for gateCheck (continuing without)', error?.message || String(error));
1723
- }
1724
- }
1725
-
1726
- const response = await this._makeRequest('GET', `/api/v1/proofs/check?${qs.toString()}`, null, mergedHeaders);
1727
- if (!response.success) {
1728
- throw new ApiError(`Gate check failed: ${response.error?.message || 'Unknown error'}`, response.error);
1729
- }
1730
- return response;
1731
- }
1732
-
1733
- async checkGate(params) {
1734
- const { walletAddress, requirements, proofs: preloadedProofs } = params;
1735
-
1736
- if (!validateUniversalAddress(walletAddress)) {
1737
- throw new ValidationError('Valid walletAddress is required');
1738
- }
1739
- if (!Array.isArray(requirements) || requirements.length === 0) {
1740
- throw new ValidationError('requirements array is required and must not be empty');
1741
- }
1742
-
1743
- let proofs = preloadedProofs;
1744
- if (!proofs) {
1745
- const result = await this.getProofsByWallet(walletAddress, { limit: 100 });
1746
- proofs = result.proofs;
1747
- }
1748
-
1749
- const now = Date.now();
1750
- const existing = {};
1751
- const missing = [];
1752
-
1753
- for (const req of requirements) {
1754
- const { verifierId, maxAgeMs, optional = false, minCount = 1, match } = req;
1755
-
1756
- const matchingProofs = (proofs || []).filter(proof => {
1757
- const verifiedVerifiers = proof.verifiedVerifiers || [];
1758
- const verifier = verifiedVerifiers.find(
1759
- v => v.verifierId === verifierId && v.verified === true
1760
- );
1761
- if (!verifier) return false;
1762
-
1763
- if (proof.revokedAt) return false;
1764
-
1765
- if (maxAgeMs) {
1766
- const proofTimestamp = proof.createdAt || proof.signedTimestamp || 0;
1767
- const proofAge = now - proofTimestamp;
1768
- if (proofAge > maxAgeMs) return false;
1769
- }
1770
-
1771
- if (match && typeof match === 'object') {
1772
- const data = verifier.data || {};
1773
- const input = data.input || {};
1774
- const matchObj = Array.isArray(match)
1775
- ? Object.fromEntries(
1776
- match
1777
- .filter((m) => m && m.path && String(m.value ?? '').trim() !== '')
1778
- .map((m) => [String(m.path).trim(), m.value])
1779
- )
1780
- : match;
1781
-
1782
- for (const [key, expected] of Object.entries(matchObj)) {
1783
- let actualValue = null;
1784
-
1785
- if (key.includes('.')) {
1786
- const parts = key.split('.');
1787
- let current = data;
1788
-
1789
- if (parts[0] === 'input' && input) {
1790
- current = input;
1791
- parts.shift();
1792
- }
1793
-
1794
- for (const part of parts) {
1795
- if (current && typeof current === 'object' && part in current) {
1796
- current = current[part];
1797
- } else {
1798
- current = undefined;
1799
- break;
1800
- }
1801
- }
1802
- actualValue = current;
1803
- } else {
1804
- actualValue = input[key] || data[key];
1805
- }
1806
-
1807
- if (key === 'content' && actualValue === undefined) {
1808
- actualValue = data.reference?.id || data.content;
1809
- }
1810
-
1811
- if (actualValue === undefined) {
1812
- const claims = data.claims || {};
1813
- if (key === 'contractAddress') {
1814
- actualValue = input.contractAddress || data.contractAddress;
1815
- } else if (key === 'tokenId') {
1816
- actualValue = input.tokenId || data.tokenId;
1817
- } else if (key === 'chainId') {
1818
- actualValue = input.chainId || data.chainId;
1819
- } else if (key === 'ownerAddress') {
1820
- actualValue = input.ownerAddress || data.owner || data.walletAddress;
1821
- } else if (key === 'minBalance') {
1822
- actualValue = input.minBalance || data.onChainData?.requiredMinBalance || data.minBalance;
1823
- } else if (key === 'primaryWalletAddress') {
1824
- actualValue = data.primaryWalletAddress;
1825
- } else if (key === 'secondaryWalletAddress') {
1826
- actualValue = data.secondaryWalletAddress;
1827
- } else if (key === 'verificationMethod') {
1828
- actualValue = data.verificationMethod;
1829
- } else if (key === 'domain') {
1830
- actualValue = data.domain;
1831
- } else if (key === 'handle') {
1832
- actualValue = data.handle || data.pseudonymId;
1833
- } else if (key === 'namespace') {
1834
- actualValue =
1835
- data.namespace !== undefined && data.namespace !== null && data.namespace !== ''
1836
- ? data.namespace
1837
- : 'neus';
1838
- } else if (key === 'claims.sanctions_passed') {
1839
- actualValue = claims.sanctions_passed ?? claims.sanctionsPassed;
1840
- } else if (key === 'claims.age_min') {
1841
- actualValue = claims.age_min ?? claims.ageMin;
1842
- } else if (key === 'neusPersonhoodId') {
1843
- actualValue = data.neusPersonhoodId;
1844
- } else if (key === 'riskLevel') {
1845
- actualValue = data.riskLevel;
1846
- } else if (key === 'sanctioned') {
1847
- actualValue = data.sanctioned;
1848
- } else if (key === 'poisoned') {
1849
- actualValue = data.poisoned;
1850
- }
1851
- }
1852
-
1853
- if (key === 'contentHash' && actualValue === undefined && data.content) {
1854
- try {
1855
- let hash = 0;
1856
- const str = String(data.content);
1857
- for (let i = 0; i < str.length; i++) {
1858
- const char = str.charCodeAt(i);
1859
- hash = ((hash << 5) - hash) + char;
1860
- hash = hash & hash;
1861
- }
1862
- actualValue = `0x${ Math.abs(hash).toString(16).padStart(64, '0').substring(0, 66)}`;
1863
- } catch {
1864
- actualValue = String(data.content);
1865
- }
1866
- }
1867
-
1868
- let normalizedActual = actualValue;
1869
- let normalizedExpected = expected;
1870
-
1871
- if (actualValue === undefined || actualValue === null) {
1872
- return false;
1873
- }
1874
-
1875
- if (typeof actualValue === 'boolean' || (key && (key.includes('sanctions') || key.includes('sanctioned') || key.includes('poisoned')))) {
1876
- const bActual = Boolean(actualValue);
1877
- const bExpected = expected === true || expected === 'true' || String(expected).toLowerCase() === 'true';
1878
- if (bActual !== bExpected) return false;
1879
- continue;
1880
- }
1881
-
1882
- if (key === 'chainId' || (key === 'tokenId' && (typeof actualValue === 'number' || !isNaN(Number(actualValue))))) {
1883
- normalizedActual = Number(actualValue);
1884
- normalizedExpected = Number(expected);
1885
- if (isNaN(normalizedActual) || isNaN(normalizedExpected)) return false;
1886
- }
1887
- else if (typeof actualValue === 'string' && (actualValue.startsWith('0x') || actualValue.length > 20)) {
1888
- normalizedActual = actualValue.toLowerCase();
1889
- normalizedExpected = typeof expected === 'string' ? String(expected).toLowerCase() : expected;
1890
- }
1891
- else {
1892
- normalizedActual = actualValue;
1893
- normalizedExpected = expected;
1894
- }
1895
-
1896
- if (normalizedActual !== normalizedExpected) {
1897
- return false;
1898
- }
1899
- }
1900
- }
1901
-
1902
- return true;
1903
- });
1904
-
1905
- if (matchingProofs.length >= minCount) {
1906
- const sorted = matchingProofs.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
1907
- existing[verifierId] = sorted[0];
1908
- } else if (!optional) {
1909
- missing.push(req);
1910
- }
1911
- }
1912
-
1913
- return {
1914
- satisfied: missing.length === 0,
1915
- missing,
1916
- existing,
1917
- allProofs: proofs
1918
- };
1919
- }
1920
-
1921
- async _getWalletAddress() {
1922
- if (typeof window === 'undefined' || !window.ethereum) {
1923
- throw new ConfigurationError('No Web3 wallet detected');
1924
- }
1925
-
1926
- const accounts = await window.ethereum.request({ method: 'eth_accounts' });
1927
- if (!accounts || accounts.length === 0) {
1928
- throw new ConfigurationError('No wallet accounts available');
1929
- }
1930
-
1931
- return accounts[0];
1932
- }
1933
-
1934
- async _makeRequest(method, endpoint, data = null, headersOverride = null) {
1935
- const url = `${this.baseUrl}${endpoint}`;
1936
-
1937
- const controller = new AbortController();
1938
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
1939
-
1940
- const options = {
1941
- method,
1942
- headers: { ...this.defaultHeaders, ...(headersOverride || {}) },
1943
- signal: controller.signal
1944
- };
1945
-
1946
- if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
1947
- options.body = JSON.stringify(data);
1948
- }
1949
-
1950
- this._log(`${method} ${endpoint}`, data ? { requestBodyKeys: Object.keys(data) } : {});
1951
-
1952
- try {
1953
- const response = await fetch(url, options);
1954
- clearTimeout(timeoutId);
1955
-
1956
- let responseData;
1957
- try {
1958
- responseData = await response.json();
1959
- } catch {
1960
- responseData = { error: { message: 'Invalid JSON response' } };
1961
- }
1962
-
1963
- if (!response.ok) {
1964
- throw ApiError.fromResponse(response, responseData);
1965
- }
1966
-
1967
- return responseData;
1968
-
1969
- } catch (error) {
1970
- clearTimeout(timeoutId);
1971
-
1972
- if (error.name === 'AbortError') {
1973
- throw new NetworkError(`Request timeout after ${this.config.timeout}ms`);
1974
- }
1975
-
1976
- if (error instanceof ApiError) {
1977
- throw error;
1978
- }
1979
-
1980
- throw new NetworkError(`Network error: ${error.message}`);
1981
- }
1982
- }
1983
-
1984
- _formatResponse(response) {
1985
- const qHash = response?.data?.qHash ||
1986
- response?.qHash ||
1987
- response?.data?.resource?.qHash ||
1988
- response?.data?.id;
1989
-
1990
- const status = response?.data?.status ||
1991
- response?.status ||
1992
- response?.data?.resource?.status ||
1993
- (response?.success ? 'completed' : 'unknown');
1994
-
1995
- return {
1996
- success: response.success,
1997
- qHash,
1998
- status,
1999
- data: response.data,
2000
- message: response.message,
2001
- timestamp: Date.now(),
2002
- proofUrl: qHash ? `${this.baseUrl}/api/v1/proofs/${qHash}` : null
2003
- };
2004
- }
2005
-
2006
- _isTerminalStatus(status) {
2007
- const terminalStates = [
2008
- 'verified',
2009
- 'verified_crosschain_propagated',
2010
- 'completed_all_successful',
2011
- 'failed',
2012
- 'error',
2013
- 'rejected',
2014
- 'cancelled'
2015
- ];
2016
- return typeof status === 'string' && terminalStates.some(state => status.includes(state));
2017
- }
2018
-
2019
- _log(message, data = {}) {
2020
- if (this.config.enableLogging) {
2021
- try {
2022
- const prefix = '[neus/sdk]';
2023
- if (data && Object.keys(data).length > 0) {
2024
- // eslint-disable-next-line no-console -- gated debug logging
2025
- console.log(prefix, message, data);
2026
- } else {
2027
- // eslint-disable-next-line no-console -- gated debug logging
2028
- console.log(prefix, message);
2029
- }
2030
- } catch {
2031
- void 0;
2032
- }
2033
- }
2034
- }
2035
- }
2036
-
2037
- export { PORTABLE_PROOF_SIGNER_HEADER, constructVerificationMessage };
1
+ import { ApiError, ValidationError, NetworkError, ConfigurationError } from './errors.js';
2
+ import { fetchSponsorGrant } from './sponsor.js';
3
+ import {
4
+ PORTABLE_PROOF_SIGNER_HEADER,
5
+ constructVerificationMessage,
6
+ validateWalletAddress,
7
+ validateUniversalAddress,
8
+ signMessage,
9
+ NEUS_CONSTANTS
10
+ } from './utils.js';
11
+
12
+ const FALLBACK_PUBLIC_VERIFIER_CATALOG = {
13
+ 'ownership-basic': { supportsDirectApi: true },
14
+ 'ownership-social': { supportsDirectApi: false },
15
+ 'ownership-pseudonym': { supportsDirectApi: true },
16
+ 'ownership-dns-txt': { supportsDirectApi: true },
17
+ 'ownership-org-oauth': { supportsDirectApi: false },
18
+ 'contract-ownership': { supportsDirectApi: true },
19
+ 'proof-of-human': { supportsDirectApi: false },
20
+ 'nft-ownership': { supportsDirectApi: true },
21
+ 'token-holding': { supportsDirectApi: true },
22
+ 'wallet-risk': { supportsDirectApi: true },
23
+ 'wallet-link': { supportsDirectApi: true },
24
+ 'ai-content-moderation': { supportsDirectApi: true },
25
+ 'agent-identity': { supportsDirectApi: true },
26
+ 'agent-delegation': { supportsDirectApi: true }
27
+ };
28
+
29
+ const EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
30
+ const WALLET_LINK_RELATIONSHIP_TYPES = new Set(['primary', 'personal', 'org', 'affiliate', 'agent', 'linked']);
31
+
32
+ function normalizeWalletLinkRelationshipType(value) {
33
+ const normalized = String(value || '').trim().toLowerCase();
34
+ return WALLET_LINK_RELATIONSHIP_TYPES.has(normalized) ? normalized : 'linked';
35
+ }
36
+
37
+ /**
38
+ * @param {unknown} raw
39
+ * @returns {string | null} Non-empty trimmed string, or null if the provider value is not a valid string identity.
40
+ */
41
+ function normalizeBrowserSignerString(raw) {
42
+ if (raw === null || raw === undefined) {
43
+ return null;
44
+ }
45
+ if (typeof raw === 'string') {
46
+ const t = raw.trim();
47
+ return t.length > 0 ? t : null;
48
+ }
49
+ return null;
50
+ }
51
+
52
+ /** Treats SDK placeholder signatures as absent so the protocol can use session or app-link delegation. */
53
+ function isPlaceholderNeusSignature(signature) {
54
+ const s = typeof signature === 'string' ? signature.trim() : '';
55
+ if (!s) return true;
56
+ return /^0x0+$/i.test(s);
57
+ }
58
+
59
+ const validateVerifierData = (verifierId, data) => {
60
+ if (!data || typeof data !== 'object') {
61
+ return { valid: false, error: 'Data object is required' };
62
+ }
63
+
64
+ switch (verifierId) {
65
+ case 'ownership-basic':
66
+ if (!data.owner || !validateUniversalAddress(data.owner, typeof data.chain === 'string' ? data.chain : undefined)) {
67
+ return { valid: false, error: 'owner (universal wallet address) is required' };
68
+ }
69
+ if (data.content !== undefined && data.content !== null) {
70
+ if (typeof data.content !== 'string') {
71
+ return { valid: false, error: 'content must be a string when provided' };
72
+ }
73
+ if (data.content.length > 50000) {
74
+ return { valid: false, error: 'content exceeds 50KB inline limit' };
75
+ }
76
+ }
77
+ if (data.contentHash !== undefined && data.contentHash !== null) {
78
+ if (typeof data.contentHash !== 'string' || !/^0x[a-fA-F0-9]{64}$/.test(data.contentHash)) {
79
+ return { valid: false, error: 'contentHash must be a 32-byte hex string (0x + 64 hex chars) when provided' };
80
+ }
81
+ }
82
+ if (data.contentType !== undefined && data.contentType !== null) {
83
+ if (typeof data.contentType !== 'string' || data.contentType.length > 100) {
84
+ return { valid: false, error: 'contentType must be a string (max 100 chars) when provided' };
85
+ }
86
+ const base = String(data.contentType).split(';')[0].trim().toLowerCase();
87
+ if (!base || base.includes(' ') || !base.includes('/')) {
88
+ return { valid: false, error: 'contentType must be a valid MIME type when provided' };
89
+ }
90
+ }
91
+ if (data.provenance !== undefined && data.provenance !== null) {
92
+ if (!data.provenance || typeof data.provenance !== 'object' || Array.isArray(data.provenance)) {
93
+ return { valid: false, error: 'provenance must be an object when provided' };
94
+ }
95
+ const dk = data.provenance.declaredKind;
96
+ if (dk !== undefined && dk !== null) {
97
+ const allowed = ['human', 'ai', 'mixed', 'unknown'];
98
+ if (typeof dk !== 'string' || !allowed.includes(dk)) {
99
+ return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(', ')}` };
100
+ }
101
+ }
102
+ const ai = data.provenance.aiContext;
103
+ if (ai !== undefined && ai !== null) {
104
+ if (typeof ai !== 'object' || Array.isArray(ai)) {
105
+ return { valid: false, error: 'provenance.aiContext must be an object when provided' };
106
+ }
107
+ if (ai.generatorType !== undefined && ai.generatorType !== null) {
108
+ const allowed = ['local', 'saas', 'agent'];
109
+ if (typeof ai.generatorType !== 'string' || !allowed.includes(ai.generatorType)) {
110
+ return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(', ')}` };
111
+ }
112
+ }
113
+ if (ai.provider !== undefined && ai.provider !== null) {
114
+ if (typeof ai.provider !== 'string' || ai.provider.length > 64) {
115
+ return { valid: false, error: 'provenance.aiContext.provider must be a string (max 64 chars) when provided' };
116
+ }
117
+ }
118
+ if (ai.model !== undefined && ai.model !== null) {
119
+ if (typeof ai.model !== 'string' || ai.model.length > 128) {
120
+ return { valid: false, error: 'provenance.aiContext.model must be a string (max 128 chars) when provided' };
121
+ }
122
+ }
123
+ if (ai.runId !== undefined && ai.runId !== null) {
124
+ if (typeof ai.runId !== 'string' || ai.runId.length > 128) {
125
+ return { valid: false, error: 'provenance.aiContext.runId must be a string (max 128 chars) when provided' };
126
+ }
127
+ }
128
+ }
129
+ }
130
+ if (data.reference !== undefined) {
131
+ if (!data.reference || typeof data.reference !== 'object') {
132
+ return { valid: false, error: 'reference must be an object when provided' };
133
+ }
134
+ if (!data.reference.type || typeof data.reference.type !== 'string') {
135
+ return { valid: false, error: 'reference.type is required when reference is provided' };
136
+ }
137
+ }
138
+ if (!data.content && !data.contentHash) {
139
+ if (!data.reference || typeof data.reference !== 'object') {
140
+ return { valid: false, error: 'reference is required when neither content nor contentHash is provided' };
141
+ }
142
+ if (!data.reference.id || typeof data.reference.id !== 'string') {
143
+ return { valid: false, error: 'reference.id is required when neither content nor contentHash is provided' };
144
+ }
145
+ if (!data.reference.type || typeof data.reference.type !== 'string') {
146
+ return { valid: false, error: 'reference.type is required when neither content nor contentHash is provided' };
147
+ }
148
+ }
149
+ break;
150
+ case 'nft-ownership':
151
+ if (
152
+ !data.contractAddress ||
153
+ data.tokenId === null ||
154
+ data.tokenId === undefined ||
155
+ typeof data.chainId !== 'number'
156
+ ) {
157
+ return { valid: false, error: 'contractAddress, tokenId, and chainId are required' };
158
+ }
159
+ if (data.tokenType !== undefined && data.tokenType !== null) {
160
+ const tt = String(data.tokenType).toLowerCase();
161
+ if (tt !== 'erc721' && tt !== 'erc1155') {
162
+ return { valid: false, error: 'tokenType must be one of: erc721, erc1155' };
163
+ }
164
+ }
165
+ if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
166
+ return { valid: false, error: 'blockNumber must be an integer when provided' };
167
+ }
168
+ if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
169
+ return { valid: false, error: 'Invalid ownerAddress' };
170
+ }
171
+ if (!validateWalletAddress(data.contractAddress)) {
172
+ return { valid: false, error: 'Invalid contractAddress' };
173
+ }
174
+ break;
175
+ case 'token-holding':
176
+ if (
177
+ !data.contractAddress ||
178
+ data.minBalance === null ||
179
+ data.minBalance === undefined ||
180
+ typeof data.chainId !== 'number'
181
+ ) {
182
+ return { valid: false, error: 'contractAddress, minBalance, and chainId are required' };
183
+ }
184
+ if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
185
+ return { valid: false, error: 'blockNumber must be an integer when provided' };
186
+ }
187
+ if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
188
+ return { valid: false, error: 'Invalid ownerAddress' };
189
+ }
190
+ if (!validateWalletAddress(data.contractAddress)) {
191
+ return { valid: false, error: 'Invalid contractAddress' };
192
+ }
193
+ break;
194
+ case 'ownership-dns-txt':
195
+ if (!data.domain || typeof data.domain !== 'string') {
196
+ return { valid: false, error: 'domain is required' };
197
+ }
198
+ if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
199
+ return { valid: false, error: 'Invalid walletAddress' };
200
+ }
201
+ break;
202
+ case 'wallet-link':
203
+ if (!data.primaryWalletAddress || !validateUniversalAddress(data.primaryWalletAddress, data.chain)) {
204
+ return { valid: false, error: 'primaryWalletAddress is required' };
205
+ }
206
+ if (!data.secondaryWalletAddress || !validateUniversalAddress(data.secondaryWalletAddress, data.chain)) {
207
+ return { valid: false, error: 'secondaryWalletAddress is required' };
208
+ }
209
+ if (!data.signature || typeof data.signature !== 'string') {
210
+ return { valid: false, error: 'signature is required (signed by secondary wallet)' };
211
+ }
212
+ if (typeof data.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
213
+ return { valid: false, error: 'chain is required (namespace:reference)' };
214
+ }
215
+ if (typeof data.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
216
+ return { valid: false, error: 'signatureMethod is required' };
217
+ }
218
+ if (typeof data.signedTimestamp !== 'number') {
219
+ return { valid: false, error: 'signedTimestamp is required' };
220
+ }
221
+ break;
222
+ case 'contract-ownership':
223
+ if (!data.contractAddress || !validateWalletAddress(data.contractAddress)) {
224
+ return { valid: false, error: 'contractAddress is required' };
225
+ }
226
+ if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
227
+ return { valid: false, error: 'Invalid walletAddress' };
228
+ }
229
+ if (typeof data.chainId !== 'number') {
230
+ return { valid: false, error: 'chainId is required' };
231
+ }
232
+ break;
233
+ case 'agent-identity':
234
+ if (!data.agentId || typeof data.agentId !== 'string' || data.agentId.length < 1 || data.agentId.length > 128) {
235
+ return { valid: false, error: 'agentId is required (1-128 chars)' };
236
+ }
237
+ if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
238
+ return { valid: false, error: 'agentWallet is required' };
239
+ }
240
+ if (data.agentType && !['ai', 'bot', 'service', 'automation', 'agent'].includes(data.agentType)) {
241
+ return { valid: false, error: 'agentType must be one of: ai, bot, service, automation, agent' };
242
+ }
243
+ if (data.defaultRuntime && typeof data.defaultRuntime === 'object') {
244
+ if (data.defaultRuntime.provider && typeof data.defaultRuntime.provider === 'string' && data.defaultRuntime.provider.length > 64) {
245
+ return { valid: false, error: 'defaultRuntime.provider must be 64 chars or less' };
246
+ }
247
+ if (data.defaultRuntime.model && typeof data.defaultRuntime.model === 'string' && data.defaultRuntime.model.length > 128) {
248
+ return { valid: false, error: 'defaultRuntime.model must be 128 chars or less' };
249
+ }
250
+ if (data.defaultRuntime.mode && typeof data.defaultRuntime.mode === 'string' && data.defaultRuntime.mode.length > 64) {
251
+ return { valid: false, error: 'defaultRuntime.mode must be 64 chars or less' };
252
+ }
253
+ }
254
+ break;
255
+ case 'agent-delegation':
256
+ if (!data.controllerWallet || !validateWalletAddress(data.controllerWallet)) {
257
+ return { valid: false, error: 'controllerWallet is required' };
258
+ }
259
+ if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
260
+ return { valid: false, error: 'agentWallet is required' };
261
+ }
262
+ if (data.scope && (typeof data.scope !== 'string' || data.scope.length > 128)) {
263
+ return { valid: false, error: 'scope must be a string (max 128 chars)' };
264
+ }
265
+ if (data.expiresAt && (typeof data.expiresAt !== 'number' || data.expiresAt < Date.now())) {
266
+ return { valid: false, error: 'expiresAt must be a future timestamp' };
267
+ }
268
+ if (data.model && typeof data.model === 'string' && data.model.length > 128) {
269
+ return { valid: false, error: 'model must be 128 chars or less' };
270
+ }
271
+ if (data.provider && typeof data.provider === 'string' && data.provider.length > 64) {
272
+ return { valid: false, error: 'provider must be 64 chars or less' };
273
+ }
274
+ if (data.allowedActions && Array.isArray(data.allowedActions) && data.allowedActions.length > 32) {
275
+ return { valid: false, error: 'allowedActions must have 32 items or less' };
276
+ }
277
+ if (data.deniedActions && Array.isArray(data.deniedActions) && data.deniedActions.length > 32) {
278
+ return { valid: false, error: 'deniedActions must have 32 items or less' };
279
+ }
280
+ break;
281
+ case 'ai-content-moderation':
282
+ if (!data.content || typeof data.content !== 'string') {
283
+ return { valid: false, error: 'content is required' };
284
+ }
285
+ if (!data.contentType || typeof data.contentType !== 'string') {
286
+ return { valid: false, error: 'contentType (MIME type) is required' };
287
+ }
288
+ {
289
+ const contentType = String(data.contentType).split(';')[0].trim().toLowerCase();
290
+ const validTypes = [
291
+ 'image/jpeg',
292
+ 'image/png',
293
+ 'image/webp',
294
+ 'image/gif',
295
+ 'text/plain',
296
+ 'text/markdown',
297
+ 'text/x-markdown',
298
+ 'application/json',
299
+ 'application/xml'
300
+ ];
301
+ if (!validTypes.includes(contentType)) {
302
+ return { valid: false, error: `contentType must be one of: ${validTypes.join(', ')}` };
303
+ }
304
+ const isTextual = contentType.startsWith('text/') || contentType.includes('markdown');
305
+ if (isTextual) {
306
+ try {
307
+ const maxBytes = 50 * 1024;
308
+ const bytes = (typeof TextEncoder !== 'undefined')
309
+ ? new TextEncoder().encode(data.content).length
310
+ : String(data.content).length;
311
+ if (bytes > maxBytes) {
312
+ return {
313
+ valid: false,
314
+ error: `content exceeds ${maxBytes} bytes limit for ai-content-moderation verifier (text)`
315
+ };
316
+ }
317
+ } catch {
318
+ void 0;
319
+ }
320
+ }
321
+ }
322
+ if (data.content.length > 13653334) {
323
+ return { valid: false, error: 'content exceeds 10MB limit' };
324
+ }
325
+ break;
326
+ case 'ownership-pseudonym':
327
+ if (!data.pseudonymId || typeof data.pseudonymId !== 'string') {
328
+ return { valid: false, error: 'pseudonymId is required' };
329
+ }
330
+ if (!/^[a-z0-9._-]{3,64}$/.test(data.pseudonymId.trim().toLowerCase())) {
331
+ return { valid: false, error: 'pseudonymId must be 3-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
332
+ }
333
+ if (data.namespace && typeof data.namespace === 'string') {
334
+ if (!/^[a-z0-9._-]{1,64}$/.test(data.namespace.trim().toLowerCase())) {
335
+ return { valid: false, error: 'namespace must be 1-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
336
+ }
337
+ }
338
+ break;
339
+ case 'wallet-risk':
340
+ if (data.walletAddress && !validateUniversalAddress(data.walletAddress, data.chain)) {
341
+ return { valid: false, error: 'Invalid walletAddress' };
342
+ }
343
+ break;
344
+ }
345
+
346
+ return { valid: true };
347
+ };
348
+
349
+ export class NeusClient {
350
+ constructor(config = {}) {
351
+ this.config = {
352
+ timeout: 30000,
353
+ enableLogging: false,
354
+ ...config
355
+ };
356
+
357
+ this.baseUrl = this.config.apiUrl || 'https://api.neus.network';
358
+ try {
359
+ const url = new URL(this.baseUrl);
360
+ if (url.hostname.endsWith('neus.network') && url.protocol === 'http:') {
361
+ url.protocol = 'https:';
362
+ }
363
+ this.baseUrl = url.toString().replace(/\/$/, '');
364
+ } catch {
365
+ void 0;
366
+ }
367
+ this.config.apiUrl = this.baseUrl;
368
+
369
+ this.defaultHeaders = {
370
+ 'Content-Type': 'application/json',
371
+ 'Accept': 'application/json',
372
+ 'X-Neus-Sdk': 'js'
373
+ };
374
+
375
+ if (typeof this.config.apiKey === 'string' && this.config.apiKey.trim().length > 0) {
376
+ this.defaultHeaders['Authorization'] = `Bearer ${this.config.apiKey.trim()}`;
377
+ }
378
+ if (typeof this.config.appId === 'string' && this.config.appId.trim().length > 0) {
379
+ this.defaultHeaders['X-Neus-App'] = this.config.appId.trim();
380
+ }
381
+ if (typeof this.config.paymentSignature === 'string' && this.config.paymentSignature.trim().length > 0) {
382
+ this.defaultHeaders['PAYMENT-SIGNATURE'] = this.config.paymentSignature.trim();
383
+ }
384
+ if (this.config.extraHeaders && typeof this.config.extraHeaders === 'object') {
385
+ for (const [k, v] of Object.entries(this.config.extraHeaders)) {
386
+ if (!k || v === undefined || v === null) continue;
387
+ const key = String(k).trim();
388
+ const value = String(v).trim();
389
+ if (!key || !value) continue;
390
+ this.defaultHeaders[key] = value;
391
+ }
392
+ }
393
+ try {
394
+ if (typeof window !== 'undefined' && window.location && window.location.origin) {
395
+ this.defaultHeaders['X-Client-Origin'] = window.location.origin;
396
+ }
397
+ } catch {
398
+ void 0;
399
+ }
400
+
401
+ /** @type {{ token: string, expMs: number, key: string } | null} */
402
+ this._sponsorGrantCache = null;
403
+ }
404
+
405
+ _getBillingWallet() {
406
+ const raw =
407
+ this.config.billingWallet ||
408
+ this.config.sponsorOrgWallet ||
409
+ this.config.orgWallet ||
410
+ null;
411
+ if (typeof raw !== 'string') return null;
412
+ const trimmed = raw.trim().toLowerCase();
413
+ return /^0x[a-f0-9]{40}$/.test(trimmed) ? trimmed : null;
414
+ }
415
+
416
+ _resolveIntegratorOrigin() {
417
+ if (typeof this.config.appOrigin === 'string' && this.config.appOrigin.trim()) {
418
+ return this.config.appOrigin.trim();
419
+ }
420
+ try {
421
+ if (typeof window !== 'undefined' && window.location?.origin) {
422
+ return window.location.origin;
423
+ }
424
+ } catch {
425
+ void 0;
426
+ }
427
+ return null;
428
+ }
429
+
430
+ async _resolveSponsorGrantHeaders(verifierIds = []) {
431
+ const appId = typeof this.config.appId === 'string' ? this.config.appId.trim() : '';
432
+ const orgWallet = this._getBillingWallet();
433
+ if (!appId || !orgWallet) {
434
+ return {};
435
+ }
436
+
437
+ const normalizedVerifierIds = Array.isArray(verifierIds)
438
+ ? verifierIds.map((v) => String(v || '').trim()).filter(Boolean).slice(0, 25)
439
+ : [];
440
+ const cacheKey = `${appId}:${orgWallet}:${normalizedVerifierIds.join(',')}`;
441
+ const now = Date.now();
442
+ if (
443
+ this._sponsorGrantCache &&
444
+ this._sponsorGrantCache.key === cacheKey &&
445
+ this._sponsorGrantCache.expMs > now + 30_000
446
+ ) {
447
+ return { 'X-Sponsor-Grant': this._sponsorGrantCache.token };
448
+ }
449
+
450
+ const origin = this._resolveIntegratorOrigin();
451
+ const grant = await fetchSponsorGrant({
452
+ apiUrl: this.baseUrl,
453
+ appId,
454
+ orgWallet,
455
+ verifierIds: normalizedVerifierIds,
456
+ origin
457
+ });
458
+
459
+ const expSeconds = Number(grant?.exp);
460
+ const expMs = Number.isFinite(expSeconds) && expSeconds > 0
461
+ ? expSeconds * 1000
462
+ : now + 15 * 60 * 1000;
463
+
464
+ this._sponsorGrantCache = {
465
+ key: cacheKey,
466
+ token: grant.sponsorGrant,
467
+ expMs
468
+ };
469
+
470
+ return { 'X-Sponsor-Grant': grant.sponsorGrant };
471
+ }
472
+
473
+ _getHubChainId() {
474
+ const configured = Number(this.config?.hubChainId);
475
+ if (Number.isFinite(configured) && configured > 0) return Math.floor(configured);
476
+ return NEUS_CONSTANTS.HUB_CHAIN_ID;
477
+ }
478
+
479
+ _normalizeIdentity(value) {
480
+ let raw = String(value || '').trim();
481
+ if (!raw) return '';
482
+ const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
483
+ if (didMatch && didMatch[3]) {
484
+ raw = String(didMatch[3]).trim();
485
+ }
486
+ return EVM_ADDRESS_RE.test(raw) ? raw.toLowerCase() : raw;
487
+ }
488
+
489
+ _inferChainForAddress(address, explicitChain) {
490
+ if (typeof explicitChain === 'string' && explicitChain.includes(':')) return explicitChain.trim();
491
+ const raw = String(address || '').trim();
492
+ const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
493
+ if (didMatch && didMatch[1] && didMatch[2]) {
494
+ return `${didMatch[1]}:${didMatch[2]}`;
495
+ }
496
+ if (EVM_ADDRESS_RE.test(raw)) {
497
+ return `eip155:${this._getHubChainId()}`;
498
+ }
499
+ return 'solana:mainnet';
500
+ }
501
+
502
+ async _resolveWalletSigner(wallet) {
503
+ if (!wallet) {
504
+ throw new ConfigurationError('No wallet provider available');
505
+ }
506
+
507
+ if (wallet.address !== null && wallet.address !== undefined) {
508
+ const s = normalizeBrowserSignerString(
509
+ typeof wallet.address === 'string' ? wallet.address : null
510
+ );
511
+ if (s) {
512
+ return { signerWalletAddress: s, provider: wallet };
513
+ }
514
+ }
515
+ if (wallet.publicKey && typeof wallet.publicKey.toBase58 === 'function') {
516
+ const b58 = wallet.publicKey.toBase58();
517
+ const s = normalizeBrowserSignerString(typeof b58 === 'string' ? b58 : null);
518
+ if (s) {
519
+ return { signerWalletAddress: s, provider: wallet };
520
+ }
521
+ }
522
+ if (typeof wallet.getAddress === 'function') {
523
+ const got = await wallet.getAddress().catch(() => null);
524
+ const s = normalizeBrowserSignerString(
525
+ got === null || got === undefined ? null : (typeof got === 'string' ? got : null)
526
+ );
527
+ if (s) {
528
+ return { signerWalletAddress: s, provider: wallet };
529
+ }
530
+ }
531
+ if (wallet.selectedAddress || wallet.request) {
532
+ const provider = wallet;
533
+ if (wallet.selectedAddress) {
534
+ const s = normalizeBrowserSignerString(
535
+ typeof wallet.selectedAddress === 'string' ? wallet.selectedAddress : null
536
+ );
537
+ if (s) {
538
+ return { signerWalletAddress: s, provider };
539
+ }
540
+ }
541
+ const accounts = await provider.request({ method: 'eth_accounts' });
542
+ if (!accounts || accounts.length === 0) {
543
+ throw new ConfigurationError('No wallet accounts available');
544
+ }
545
+ const s = normalizeBrowserSignerString(accounts[0]);
546
+ if (!s) {
547
+ throw new ConfigurationError('No wallet accounts available');
548
+ }
549
+ return { signerWalletAddress: s, provider };
550
+ }
551
+
552
+ throw new ConfigurationError('Invalid wallet provider');
553
+ }
554
+
555
+ _getDefaultBrowserWallet() {
556
+ if (typeof window === 'undefined') return null;
557
+ // Legacy convenience fallback only. Non-EVM wallets must be passed explicitly
558
+ // with CAIP-2 chain context so the SDK does not route them through EVM RPC.
559
+ return window.ethereum || null;
560
+ }
561
+
562
+ async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
563
+ const providerWallet = wallet || this._getDefaultBrowserWallet();
564
+ const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
565
+ if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
566
+ throw new ConfigurationError('No wallet accounts available');
567
+ }
568
+
569
+ const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
570
+ const normalizedAddress = this._normalizeIdentity(address);
571
+ if (!normalizedSigner || normalizedSigner !== normalizedAddress) {
572
+ throw new ValidationError('wallet must match address when includePrivate=true');
573
+ }
574
+
575
+ const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
576
+ const resolvedChain = this._inferChainForAddress(normalizedSigner, chain);
577
+ const resolvedSignatureMethod = (typeof signatureMethod === 'string' && signatureMethod.trim())
578
+ ? signatureMethod.trim()
579
+ : (signerIsEvm ? 'eip191' : 'ed25519');
580
+
581
+ const signedTimestamp = Date.now();
582
+ const message = constructVerificationMessage({
583
+ walletAddress: signerWalletAddress,
584
+ signedTimestamp,
585
+ data: { action: 'gate_check_private_proofs', walletAddress: normalizedAddress },
586
+ verifierIds: ['ownership-basic'],
587
+ ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain: resolvedChain })
588
+ });
589
+
590
+ let signature;
591
+ try {
592
+ signature = await signMessage({
593
+ provider,
594
+ message,
595
+ walletAddress: signerWalletAddress,
596
+ ...(signerIsEvm ? {} : { chain: resolvedChain })
597
+ });
598
+ } catch (error) {
599
+ if (error.code === 4001) {
600
+ throw new ValidationError('User rejected signature request');
601
+ }
602
+ throw new ValidationError(`Failed to sign message: ${error.message}`);
603
+ }
604
+
605
+ return {
606
+ walletAddress: signerWalletAddress,
607
+ signature,
608
+ signedTimestamp,
609
+ ...(signerIsEvm ? {} : { chain: resolvedChain, signatureMethod: resolvedSignatureMethod })
610
+ };
611
+ }
612
+
613
+ async createGatePrivateAuth(params = {}) {
614
+ const address = (params.address || '').toString();
615
+ if (!validateUniversalAddress(address, params.chain)) {
616
+ throw new ValidationError('Valid address is required');
617
+ }
618
+ return this._buildPrivateGateAuth({
619
+ address,
620
+ wallet: params.wallet,
621
+ chain: params.chain,
622
+ signatureMethod: params.signatureMethod
623
+ });
624
+ }
625
+
626
+ async createWalletLinkData(params = {}) {
627
+ const normalizedPrimary = this._normalizeIdentity(params.primaryWalletAddress);
628
+ const normalizedSecondary = this._normalizeIdentity(params.secondaryWalletAddress);
629
+
630
+ if (!EVM_ADDRESS_RE.test(normalizedPrimary)) {
631
+ throw new ValidationError('wallet-link requires a valid primaryWalletAddress');
632
+ }
633
+ if (!EVM_ADDRESS_RE.test(normalizedSecondary)) {
634
+ throw new ValidationError('wallet-link requires a valid secondaryWalletAddress');
635
+ }
636
+ if (normalizedPrimary === normalizedSecondary) {
637
+ throw new ValidationError('wallet-link secondaryWalletAddress must differ from primaryWalletAddress');
638
+ }
639
+
640
+ const providerWallet = params.wallet || this._getDefaultBrowserWallet();
641
+ const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
642
+ const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
643
+ if (!EVM_ADDRESS_RE.test(normalizedSigner)) {
644
+ throw new ValidationError('wallet-link requires an EVM wallet/provider for the secondary wallet');
645
+ }
646
+ if (normalizedSigner !== normalizedSecondary) {
647
+ throw new ValidationError('wallet-link wallet/provider must be connected to secondaryWalletAddress');
648
+ }
649
+
650
+ const resolvedChain = (() => {
651
+ const raw = String(params.chain || '').trim();
652
+ if (!raw) return `eip155:${this._getHubChainId()}`;
653
+ if (!/^eip155:\d+$/.test(raw)) {
654
+ throw new ValidationError('wallet-link requires chain in the form eip155:<chainId>');
655
+ }
656
+ return raw;
657
+ })();
658
+ const signedTimestamp = Number.isFinite(Number(params.signedTimestamp)) && Number(params.signedTimestamp) > 0
659
+ ? Number(params.signedTimestamp)
660
+ : Date.now();
661
+ const linkData = {
662
+ primaryAccountId: `${resolvedChain}:${normalizedPrimary}`,
663
+ secondaryAccountId: `${resolvedChain}:${normalizedSecondary}`,
664
+ primaryWalletAddress: normalizedPrimary,
665
+ secondaryWalletAddress: normalizedSecondary
666
+ };
667
+ const message = constructVerificationMessage({
668
+ walletAddress: normalizedSecondary,
669
+ signedTimestamp,
670
+ data: linkData,
671
+ verifierIds: ['wallet-link'],
672
+ chain: resolvedChain
673
+ });
674
+
675
+ let signature;
676
+ try {
677
+ signature = await signMessage({
678
+ provider,
679
+ message,
680
+ walletAddress: signerWalletAddress
681
+ });
682
+ } catch (error) {
683
+ if (error?.code === 4001) {
684
+ throw new ValidationError('User rejected wallet-link signature request');
685
+ }
686
+ throw new ValidationError(`Failed to sign wallet-link message: ${error?.message || String(error)}`);
687
+ }
688
+
689
+ const label = String(params.label || '').trim().slice(0, 64);
690
+ return {
691
+ primaryWalletAddress: normalizedPrimary,
692
+ secondaryWalletAddress: normalizedSecondary,
693
+ signature,
694
+ chain: resolvedChain,
695
+ signatureMethod: 'eip191',
696
+ signedTimestamp,
697
+ relationshipType: normalizeWalletLinkRelationshipType(params.relationshipType),
698
+ ...(label ? { label } : {})
699
+ };
700
+ }
701
+
702
+ async verifyFromApp(params) {
703
+ if (!params || typeof params !== 'object') {
704
+ throw new ValidationError('verifyFromApp requires a params object');
705
+ }
706
+
707
+ const {
708
+ user,
709
+ verifier = 'ownership-basic',
710
+ content,
711
+ data: explicitData = null,
712
+ options = {}
713
+ } = params;
714
+
715
+ if (!user || typeof user !== 'object') {
716
+ throw new ValidationError('verifyFromApp requires user object');
717
+ }
718
+
719
+ const walletAddress =
720
+ user.walletAddress ||
721
+ user.address ||
722
+ user.identity;
723
+ if (!walletAddress || typeof walletAddress !== 'string') {
724
+ throw new ValidationError('verifyFromApp requires user.walletAddress');
725
+ }
726
+
727
+ const delegationQHash =
728
+ this.config.appLinkQHash ||
729
+ (typeof process !== 'undefined' && process.env && process.env.NEUS_APP_LINK_QHASH) ||
730
+ null;
731
+
732
+ let data;
733
+ if (explicitData && typeof explicitData === 'object') {
734
+ data = { owner: walletAddress, ...explicitData };
735
+ } else if (content && typeof content === 'object') {
736
+ data = {
737
+ owner: walletAddress,
738
+ content: JSON.stringify(content),
739
+ contentType: typeof content.contentType === 'string' ? content.contentType : 'application/json',
740
+ reference: content.reference || { type: 'other' }
741
+ };
742
+ } else if (typeof content === 'string') {
743
+ data = {
744
+ owner: walletAddress,
745
+ content,
746
+ reference: { type: 'other' }
747
+ };
748
+ } else {
749
+ data = { owner: walletAddress, reference: { type: 'other' } };
750
+ }
751
+
752
+ const signedTimestamp = Date.now();
753
+ const verifierIds = [verifier];
754
+
755
+ return this.verify({
756
+ verifierIds,
757
+ data,
758
+ walletAddress,
759
+ signedTimestamp,
760
+ ...(delegationQHash ? { delegationQHash } : {}),
761
+ options
762
+ });
763
+ }
764
+
765
+ async verify(params) {
766
+ if (
767
+ (isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) &&
768
+ (params?.verifier || params?.content || params?.data)
769
+ ) {
770
+ const { content, verifier = 'ownership-basic', data = null, wallet = null, options = {} } = params;
771
+
772
+ if (verifier === 'ownership-basic' && !data && (!content || typeof content !== 'string')) {
773
+ throw new ValidationError('content is required and must be a string (or use data param with owner + reference)');
774
+ }
775
+
776
+ let verifierCatalog = FALLBACK_PUBLIC_VERIFIER_CATALOG;
777
+ try {
778
+ const discovered = await this.getVerifierCatalog();
779
+ if (discovered && discovered.metadata && Object.keys(discovered.metadata).length > 0) {
780
+ verifierCatalog = discovered.metadata;
781
+ }
782
+ } catch {
783
+ void 0;
784
+ }
785
+ const validVerifiers = Object.keys(verifierCatalog);
786
+ if (!validVerifiers.includes(verifier)) {
787
+ throw new ValidationError(`Invalid verifier '${verifier}'. Must be one of: ${validVerifiers.join(', ')}.`);
788
+ }
789
+
790
+ if (verifierCatalog?.[verifier]?.supportsDirectApi === false) {
791
+ const hostedCheckoutUrl = options?.hostedCheckoutUrl || 'https://neus.network/verify';
792
+ throw new ValidationError(
793
+ `${verifier} requires hosted interactive checkout. Use VerifyGate or redirect to ${hostedCheckoutUrl}.`
794
+ );
795
+ }
796
+
797
+ const requiresDataParam = [
798
+ 'ownership-dns-txt',
799
+ 'wallet-link',
800
+ 'contract-ownership',
801
+ 'ownership-pseudonym',
802
+ 'wallet-risk',
803
+ 'agent-identity',
804
+ 'agent-delegation',
805
+ 'ai-content-moderation'
806
+ ];
807
+ if (requiresDataParam.includes(verifier)) {
808
+ if (!data || typeof data !== 'object') {
809
+ throw new ValidationError(`${verifier} requires explicit data parameter. Cannot use auto-path.`);
810
+ }
811
+ }
812
+
813
+ let walletAddress, provider;
814
+ if (wallet) {
815
+ walletAddress =
816
+ wallet.address ||
817
+ wallet.selectedAddress ||
818
+ wallet.walletAddress ||
819
+ (typeof wallet.getAddress === 'function' ? await wallet.getAddress().catch(() => null) : null);
820
+ provider = wallet.provider || wallet;
821
+ if (!walletAddress && provider && typeof provider.request === 'function') {
822
+ let accounts = await provider.request({ method: 'eth_accounts' });
823
+ walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
824
+ if (!walletAddress) {
825
+ await provider.request({ method: 'eth_requestAccounts' });
826
+ accounts = await provider.request({ method: 'eth_accounts' });
827
+ walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
828
+ }
829
+ }
830
+ } else {
831
+ if (typeof window === 'undefined' || !window.ethereum) {
832
+ throw new ConfigurationError('No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.');
833
+ }
834
+ await window.ethereum.request({ method: 'eth_requestAccounts' });
835
+ provider = window.ethereum;
836
+ const accounts = await provider.request({ method: 'eth_accounts' });
837
+ walletAddress = accounts[0];
838
+ }
839
+
840
+ {
841
+ const normalized = normalizeBrowserSignerString(
842
+ typeof walletAddress === 'string' ? walletAddress : null
843
+ );
844
+ if (!normalized) {
845
+ throw new ConfigurationError('No wallet accounts available. Connect a wallet to continue.');
846
+ }
847
+ walletAddress = normalized;
848
+ }
849
+
850
+ let verificationData;
851
+ if (verifier === 'ownership-basic') {
852
+ if (data && typeof data === 'object') {
853
+ verificationData = {
854
+ owner: data.owner || walletAddress,
855
+ reference: data.reference,
856
+ ...(data.content && { content: data.content }),
857
+ ...(data.contentHash && { contentHash: data.contentHash }),
858
+ ...(data.contentType && { contentType: data.contentType }),
859
+ ...(data.provenance && { provenance: data.provenance })
860
+ };
861
+ } else {
862
+ verificationData = {
863
+ content: content,
864
+ owner: walletAddress,
865
+ reference: { type: 'other' }
866
+ };
867
+ }
868
+ } else if (verifier === 'token-holding') {
869
+ if (!data?.contractAddress) {
870
+ throw new ValidationError('token-holding requires contractAddress in data parameter');
871
+ }
872
+ if (data?.minBalance === null || data?.minBalance === undefined) {
873
+ throw new ValidationError('token-holding requires minBalance in data parameter');
874
+ }
875
+ if (typeof data?.chainId !== 'number') {
876
+ throw new ValidationError('token-holding requires chainId (number) in data parameter');
877
+ }
878
+ verificationData = {
879
+ ownerAddress: walletAddress,
880
+ contractAddress: data.contractAddress,
881
+ minBalance: data.minBalance,
882
+ chainId: data.chainId
883
+ };
884
+ } else if (verifier === 'nft-ownership') {
885
+ if (!data?.contractAddress) {
886
+ throw new ValidationError('nft-ownership requires contractAddress in data parameter');
887
+ }
888
+ if (data?.tokenId === null || data?.tokenId === undefined) {
889
+ throw new ValidationError('nft-ownership requires tokenId in data parameter');
890
+ }
891
+ if (typeof data?.chainId !== 'number') {
892
+ throw new ValidationError('nft-ownership requires chainId (number) in data parameter');
893
+ }
894
+ verificationData = {
895
+ ownerAddress: walletAddress,
896
+ contractAddress: data.contractAddress,
897
+ tokenId: data.tokenId,
898
+ chainId: data.chainId,
899
+ tokenType: data?.tokenType || 'erc721'
900
+ };
901
+ } else if (verifier === 'ownership-dns-txt') {
902
+ if (!data?.domain) {
903
+ throw new ValidationError('ownership-dns-txt requires domain in data parameter');
904
+ }
905
+ verificationData = {
906
+ domain: data.domain,
907
+ walletAddress: walletAddress
908
+ };
909
+ } else if (verifier === 'wallet-link') {
910
+ if (!data?.secondaryWalletAddress) {
911
+ throw new ValidationError('wallet-link requires secondaryWalletAddress in data parameter');
912
+ }
913
+ if (!data?.signature) {
914
+ throw new ValidationError('wallet-link requires signature in data parameter (signed by secondary wallet)');
915
+ }
916
+ if (typeof data?.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
917
+ throw new ValidationError('wallet-link requires chain (namespace:reference) in data parameter');
918
+ }
919
+ if (typeof data?.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
920
+ throw new ValidationError('wallet-link requires signatureMethod in data parameter');
921
+ }
922
+ verificationData = {
923
+ primaryWalletAddress: walletAddress,
924
+ secondaryWalletAddress: data.secondaryWalletAddress,
925
+ signature: data.signature,
926
+ chain: data.chain,
927
+ signatureMethod: data.signatureMethod,
928
+ signedTimestamp: data?.signedTimestamp || Date.now()
929
+ };
930
+ } else if (verifier === 'contract-ownership') {
931
+ if (!data?.contractAddress) {
932
+ throw new ValidationError('contract-ownership requires contractAddress in data parameter');
933
+ }
934
+ if (typeof data?.chainId !== 'number') {
935
+ throw new ValidationError('contract-ownership requires chainId (number) in data parameter');
936
+ }
937
+ verificationData = {
938
+ contractAddress: data.contractAddress,
939
+ walletAddress: walletAddress,
940
+ chainId: data.chainId,
941
+ ...(data?.method && { method: data.method })
942
+ };
943
+ } else if (verifier === 'agent-identity') {
944
+ if (!data?.agentId) {
945
+ throw new ValidationError('agent-identity requires agentId in data parameter');
946
+ }
947
+ verificationData = {
948
+ agentId: data.agentId,
949
+ agentWallet: data?.agentWallet || walletAddress,
950
+ ...(data?.agentChainRef && { agentChainRef: data.agentChainRef }),
951
+ ...(data?.agentAccountId && { agentAccountId: data.agentAccountId }),
952
+ ...(data?.agentLabel && { agentLabel: data.agentLabel }),
953
+ ...(data?.agentType && { agentType: data.agentType }),
954
+ ...(data?.avatar && { avatar: data.avatar }),
955
+ ...(data?.description && { description: data.description }),
956
+ ...(data?.defaultRuntime && { defaultRuntime: data.defaultRuntime }),
957
+ ...(data?.capabilities && { capabilities: data.capabilities }),
958
+ ...(data?.instructions && { instructions: data.instructions }),
959
+ ...(data?.skills && { skills: data.skills }),
960
+ ...(data?.services && { services: data.services })
961
+ };
962
+ } else if (verifier === 'agent-delegation') {
963
+ if (!data?.agentWallet) {
964
+ throw new ValidationError('agent-delegation requires agentWallet in data parameter');
965
+ }
966
+ verificationData = {
967
+ controllerWallet: data?.controllerWallet || walletAddress,
968
+ ...(data?.controllerChainRef && { controllerChainRef: data.controllerChainRef }),
969
+ agentWallet: data.agentWallet,
970
+ ...(data?.agentChainRef && { agentChainRef: data.agentChainRef }),
971
+ ...(data?.controllerAccountId && { controllerAccountId: data.controllerAccountId }),
972
+ ...(data?.agentAccountId && { agentAccountId: data.agentAccountId }),
973
+ ...(data?.agentId && { agentId: data.agentId }),
974
+ ...(data?.scope && { scope: data.scope }),
975
+ ...(data?.permissions && { permissions: data.permissions }),
976
+ ...(data?.maxSpend && { maxSpend: data.maxSpend }),
977
+ ...(data?.allowedPaymentTypes && { allowedPaymentTypes: data.allowedPaymentTypes }),
978
+ ...(data?.receiptDisclosure && { receiptDisclosure: data.receiptDisclosure }),
979
+ ...(data?.expiresAt && { expiresAt: data.expiresAt }),
980
+ ...(data?.instructions && { instructions: data.instructions }),
981
+ ...(data?.skills && { skills: data.skills }),
982
+ ...(data?.model && { model: data.model }),
983
+ ...(data?.provider && { provider: data.provider }),
984
+ ...(data?.runtimePolicy && { runtimePolicy: data.runtimePolicy }),
985
+ ...(data?.allowedActions && { allowedActions: data.allowedActions }),
986
+ ...(data?.deniedActions && { deniedActions: data.deniedActions }),
987
+ ...(data?.approvalPolicy && { approvalPolicy: data.approvalPolicy })
988
+ };
989
+ } else if (verifier === 'ai-content-moderation') {
990
+ if (!data?.content) {
991
+ throw new ValidationError('ai-content-moderation requires content (base64) in data parameter');
992
+ }
993
+ if (!data?.contentType) {
994
+ throw new ValidationError('ai-content-moderation requires contentType (MIME type) in data parameter');
995
+ }
996
+ verificationData = {
997
+ content: data.content,
998
+ contentType: data.contentType,
999
+ ...(data?.provider && { provider: data.provider })
1000
+ };
1001
+ } else if (verifier === 'ownership-pseudonym') {
1002
+ if (!data?.pseudonymId) {
1003
+ throw new ValidationError('ownership-pseudonym requires pseudonymId in data parameter');
1004
+ }
1005
+ verificationData = {
1006
+ pseudonymId: data.pseudonymId,
1007
+ ...(data?.namespace && { namespace: data.namespace }),
1008
+ ...(data?.displayName && { displayName: data.displayName }),
1009
+ ...(data?.metadata && { metadata: data.metadata })
1010
+ };
1011
+ } else if (verifier === 'wallet-risk') {
1012
+ verificationData = {
1013
+ walletAddress: data?.walletAddress || walletAddress,
1014
+ ...(data?.provider && { provider: data.provider }),
1015
+ chainId: (typeof data?.chainId === 'number' && Number.isFinite(data.chainId)) ? data.chainId : 1,
1016
+ ...(data?.includeDetails !== undefined && { includeDetails: data.includeDetails })
1017
+ };
1018
+ } else {
1019
+ verificationData = data ? {
1020
+ content,
1021
+ owner: walletAddress,
1022
+ ...data
1023
+ } : {
1024
+ content,
1025
+ owner: walletAddress
1026
+ };
1027
+ }
1028
+
1029
+ const signedTimestamp = Date.now();
1030
+ const verifierIds = [verifier];
1031
+ const message = constructVerificationMessage({
1032
+ walletAddress,
1033
+ signedTimestamp,
1034
+ data: verificationData,
1035
+ verifierIds,
1036
+ chainId: this._getHubChainId()
1037
+ });
1038
+
1039
+ let signature;
1040
+ try {
1041
+ const toHexUtf8 = (s) => {
1042
+ try {
1043
+ const enc = new TextEncoder();
1044
+ const bytes = enc.encode(s);
1045
+ let hex = '0x';
1046
+ for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, '0');
1047
+ return hex;
1048
+ } catch {
1049
+ let hex = '0x';
1050
+ for (let i = 0; i < s.length; i++) hex += s.charCodeAt(i).toString(16).padStart(2, '0');
1051
+ return hex;
1052
+ }
1053
+ };
1054
+
1055
+ const isBaseMiniAppWallet = (() => {
1056
+ if (typeof window === 'undefined') return false;
1057
+ try {
1058
+ const w = window;
1059
+ const mini = w.mini;
1060
+ if (!mini) return false;
1061
+ const miniProvider = mini.wallet || mini.provider;
1062
+ if (miniProvider === provider) return true;
1063
+ if (w.ethereum === provider && mini) return true;
1064
+ } catch {
1065
+ void 0;
1066
+ }
1067
+ return false;
1068
+ })();
1069
+
1070
+ if (isBaseMiniAppWallet) {
1071
+ try {
1072
+ const hexMsg = toHexUtf8(message);
1073
+ signature = await provider.request({ method: 'personal_sign', params: [hexMsg, walletAddress] });
1074
+ } catch (e) {
1075
+ void e;
1076
+ }
1077
+ }
1078
+
1079
+ if (!signature) {
1080
+ try {
1081
+ signature = await provider.request({ method: 'personal_sign', params: [message, walletAddress] });
1082
+ } catch (e) {
1083
+ const msg = String(e && (e.message || e.reason) || e || '').toLowerCase();
1084
+ const errCode = (e && (e.code || (e.error && e.error.code))) || null;
1085
+ const needsHex = /byte|bytes|invalid byte sequence|encoding|non-hex/i.test(msg);
1086
+
1087
+ const unsupportedRe =
1088
+ /method.*not.*supported|unsupported|not implemented|method not found|unknown method|does not support/i;
1089
+ const methodUnsupported = (
1090
+ unsupportedRe.test(msg) ||
1091
+ errCode === -32601 ||
1092
+ errCode === 4200 ||
1093
+ (msg.includes('personal_sign') && msg.includes('not')) ||
1094
+ (msg.includes('request method') && msg.includes('not supported'))
1095
+ );
1096
+
1097
+ if (methodUnsupported) {
1098
+ this._log('personal_sign not supported; attempting eth_sign fallback');
1099
+ try {
1100
+ const enc = new TextEncoder();
1101
+ const bytes = enc.encode(message);
1102
+ const prefix = `\x19Ethereum Signed Message:\n${bytes.length}`;
1103
+ const full = new Uint8Array(prefix.length + bytes.length);
1104
+ for (let i = 0; i < prefix.length; i++) full[i] = prefix.charCodeAt(i);
1105
+ full.set(bytes, prefix.length);
1106
+ let payloadHex = '0x';
1107
+ for (let i = 0; i < full.length; i++) payloadHex += full[i].toString(16).padStart(2, '0');
1108
+ try {
1109
+ if (typeof window !== 'undefined') window.__NEUS_ALLOW_ETH_SIGN__ = true;
1110
+ signature = await provider.request({ method: 'eth_sign', params: [walletAddress, payloadHex], neusAllowEthSign: true });
1111
+ } finally {
1112
+ try { if (typeof window !== 'undefined') delete window.__NEUS_ALLOW_ETH_SIGN__; } catch { void 0; }
1113
+ }
1114
+ } catch (fallbackErr) {
1115
+ this._log('eth_sign fallback failed', { message: fallbackErr?.message || String(fallbackErr) });
1116
+ throw e;
1117
+ }
1118
+ } else if (needsHex) {
1119
+ this._log('Retrying personal_sign with hex-encoded message');
1120
+ const hexMsg = toHexUtf8(message);
1121
+ signature = await provider.request({ method: 'personal_sign', params: [hexMsg, walletAddress] });
1122
+ } else {
1123
+ throw e;
1124
+ }
1125
+ }
1126
+ }
1127
+ } catch (error) {
1128
+ if (error.code === 4001) {
1129
+ throw new ValidationError('User rejected the signature request. Signature is required to create proofs.');
1130
+ }
1131
+ throw new ValidationError(`Failed to sign verification message: ${error.message}`);
1132
+ }
1133
+
1134
+ return this.verify({
1135
+ verifierIds,
1136
+ data: verificationData,
1137
+ walletAddress,
1138
+ signature,
1139
+ signedTimestamp,
1140
+ options
1141
+ });
1142
+ }
1143
+
1144
+ const {
1145
+ verifierIds,
1146
+ data,
1147
+ walletAddress,
1148
+ signature: rawSignature,
1149
+ signedTimestamp,
1150
+ chainId,
1151
+ chain,
1152
+ signatureMethod,
1153
+ delegationQHash,
1154
+ options = {}
1155
+ } = params;
1156
+
1157
+ const signature = isPlaceholderNeusSignature(rawSignature) ? undefined : rawSignature;
1158
+
1159
+ const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
1160
+
1161
+ const normalizeVerifierId = (id) => {
1162
+ if (typeof id !== 'string') return id;
1163
+ const match = id.match(/^(.*)@\d+$/);
1164
+ return match ? match[1] : id;
1165
+ };
1166
+ const normalizedVerifierIds = Array.isArray(verifierIds) ? verifierIds.map(normalizeVerifierId) : [];
1167
+
1168
+ if (!normalizedVerifierIds || normalizedVerifierIds.length === 0) {
1169
+ throw new ValidationError('verifierIds array is required');
1170
+ }
1171
+ if (!data || typeof data !== 'object') {
1172
+ throw new ValidationError('data object is required');
1173
+ }
1174
+ if (!walletAddress || typeof walletAddress !== 'string') {
1175
+ throw new ValidationError('walletAddress is required');
1176
+ }
1177
+ const hasAppAttribution =
1178
+ typeof this.config.appId === 'string' && this.config.appId.trim().length > 0;
1179
+ if (!signature && !delegationQHash && !hasAppAttribution) {
1180
+ throw new ValidationError(
1181
+ 'signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required'
1182
+ );
1183
+ }
1184
+ if (!signedTimestamp || typeof signedTimestamp !== 'number') {
1185
+ throw new ValidationError('signedTimestamp is required');
1186
+ }
1187
+ if (resolvedChainId !== null && typeof resolvedChainId !== 'number') {
1188
+ throw new ValidationError('chainId must be a number');
1189
+ }
1190
+
1191
+ for (const verifierId of normalizedVerifierIds) {
1192
+ const validation = validateVerifierData(verifierId, data);
1193
+ if (!validation.valid) {
1194
+ throw new ValidationError(`Validation failed for verifier '${verifierId}': ${validation.error}`);
1195
+ }
1196
+ }
1197
+
1198
+ const optionsPayload = {
1199
+ ...(options && typeof options === 'object' ? options : {}),
1200
+ targetChains: Array.isArray(options?.targetChains) ? options.targetChains : [],
1201
+ privacyLevel: options?.privacyLevel || 'private',
1202
+ publicDisplay: options?.publicDisplay || false,
1203
+ storeOriginalContent:
1204
+ typeof options?.storeOriginalContent === 'boolean' ? options.storeOriginalContent : true
1205
+ };
1206
+ if (typeof options?.enableIpfs === 'boolean') optionsPayload.enableIpfs = options.enableIpfs;
1207
+ // Receipts persist offchain by default; hub registry anchoring is explicit opt-in.
1208
+ if (options?.publishToHub === true) {
1209
+ optionsPayload.publishToHub = true;
1210
+ } else {
1211
+ delete optionsPayload.publishToHub;
1212
+ }
1213
+
1214
+ const requestData = {
1215
+ verifierIds: normalizedVerifierIds,
1216
+ data,
1217
+ walletAddress,
1218
+ ...(signature ? { signature } : {}),
1219
+ signedTimestamp,
1220
+ ...(resolvedChainId !== null && { chainId: resolvedChainId }),
1221
+ ...(chain && { chain }),
1222
+ ...(signatureMethod && { signatureMethod }),
1223
+ ...(delegationQHash && { delegationQHash }),
1224
+ options: optionsPayload
1225
+ };
1226
+
1227
+ const sponsorHeaders = await this._resolveSponsorGrantHeaders(normalizedVerifierIds);
1228
+ const response = await this._makeRequest('POST', '/api/v1/verification', requestData, sponsorHeaders);
1229
+
1230
+ if (!response.success) {
1231
+ throw new ApiError(`Verification failed: ${response.error?.message || 'Unknown error'}`, response.error);
1232
+ }
1233
+
1234
+ return this._formatResponse(response);
1235
+ }
1236
+
1237
+ async getProof(qHash) {
1238
+ if (!qHash || typeof qHash !== 'string') {
1239
+ throw new ValidationError('qHash is required');
1240
+ }
1241
+ const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`);
1242
+
1243
+ if (!response.success) {
1244
+ throw new ApiError(`Failed to get proof: ${response.error?.message || 'Unknown error'}`, response.error);
1245
+ }
1246
+
1247
+ return this._formatResponse(response);
1248
+ }
1249
+
1250
+ async getPrivateProof(qHash, wallet = null) {
1251
+ if (!qHash || typeof qHash !== 'string') {
1252
+ throw new ValidationError('qHash is required');
1253
+ }
1254
+
1255
+ const isPreSignedAuth = wallet &&
1256
+ typeof wallet === 'object' &&
1257
+ typeof wallet.walletAddress === 'string' &&
1258
+ typeof wallet.signature === 'string' &&
1259
+ typeof wallet.signedTimestamp === 'number';
1260
+ if (isPreSignedAuth) {
1261
+ const auth = wallet;
1262
+ const headers = {
1263
+ 'x-wallet-address': String(auth.walletAddress),
1264
+ 'x-signature': String(auth.signature),
1265
+ 'x-signed-timestamp': String(auth.signedTimestamp),
1266
+ ...(typeof auth.chain === 'string' && auth.chain.trim() ? { 'x-chain': auth.chain.trim() } : {}),
1267
+ ...(typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim() ? { 'x-signature-method': auth.signatureMethod.trim() } : {})
1268
+ };
1269
+ const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`, null, headers);
1270
+ if (!response.success) {
1271
+ throw new ApiError(
1272
+ `Failed to access private proof: ${response.error?.message || 'Unauthorized'}`,
1273
+ response.error
1274
+ );
1275
+ }
1276
+ return this._formatResponse(response);
1277
+ }
1278
+
1279
+ const providerWallet = wallet || this._getDefaultBrowserWallet();
1280
+ const { signerWalletAddress: walletAddress, provider } = await this._resolveWalletSigner(providerWallet);
1281
+ if (!walletAddress || typeof walletAddress !== 'string') {
1282
+ throw new ConfigurationError('No wallet accounts available');
1283
+ }
1284
+ const signerIsEvm = EVM_ADDRESS_RE.test(this._normalizeIdentity(walletAddress));
1285
+ const chain = this._inferChainForAddress(walletAddress);
1286
+ const signatureMethod = signerIsEvm ? 'eip191' : 'ed25519';
1287
+
1288
+ const signedTimestamp = Date.now();
1289
+
1290
+ const message = constructVerificationMessage({
1291
+ walletAddress,
1292
+ signedTimestamp,
1293
+ data: { action: 'access_private_proof', qHash: qHash },
1294
+ verifierIds: ['ownership-basic'],
1295
+ ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
1296
+ });
1297
+
1298
+ let signature;
1299
+ try {
1300
+ signature = await signMessage({
1301
+ provider,
1302
+ message,
1303
+ walletAddress,
1304
+ ...(signerIsEvm ? {} : { chain })
1305
+ });
1306
+ } catch (error) {
1307
+ if (error.code === 4001) {
1308
+ throw new ValidationError('User rejected signature request');
1309
+ }
1310
+ throw new ValidationError(`Failed to sign message: ${error.message}`);
1311
+ }
1312
+
1313
+ const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`, null, {
1314
+ 'x-wallet-address': walletAddress,
1315
+ 'x-signature': signature,
1316
+ 'x-signed-timestamp': signedTimestamp.toString(),
1317
+ ...(signerIsEvm ? {} : { 'x-chain': chain, 'x-signature-method': signatureMethod })
1318
+ });
1319
+
1320
+ if (!response.success) {
1321
+ throw new ApiError(
1322
+ `Failed to access private proof: ${response.error?.message || 'Unauthorized'}`,
1323
+ response.error
1324
+ );
1325
+ }
1326
+
1327
+ return this._formatResponse(response);
1328
+ }
1329
+
1330
+ async isHealthy() {
1331
+ try {
1332
+ const response = await this._makeRequest('GET', '/api/v1/health');
1333
+ return response.success === true;
1334
+ } catch {
1335
+ return false;
1336
+ }
1337
+ }
1338
+
1339
+ async getVerifiers() {
1340
+ const catalog = await this.getVerifierCatalog();
1341
+ return Array.isArray(catalog?.data) ? catalog.data : [];
1342
+ }
1343
+
1344
+ async getVerifierCatalog() {
1345
+ const response = await this._makeRequest('GET', '/api/v1/verification/verifiers');
1346
+ if (!response.success) {
1347
+ throw new ApiError(`Failed to get verifiers: ${response.error?.message || 'Unknown error'}`, response.error);
1348
+ }
1349
+ return {
1350
+ data: Array.isArray(response.data) ? response.data : [],
1351
+ metadata:
1352
+ response.metadata && typeof response.metadata === 'object' && !Array.isArray(response.metadata)
1353
+ ? response.metadata
1354
+ : {},
1355
+ meta:
1356
+ response.meta && typeof response.meta === 'object' && !Array.isArray(response.meta)
1357
+ ? response.meta
1358
+ : {}
1359
+ };
1360
+ }
1361
+
1362
+ async pollProofStatus(qHash, options = {}) {
1363
+ const {
1364
+ interval = 5000,
1365
+ timeout = 120000,
1366
+ onProgress
1367
+ } = options;
1368
+
1369
+ if (!qHash || typeof qHash !== 'string') {
1370
+ throw new ValidationError('qHash is required');
1371
+ }
1372
+
1373
+ const startTime = Date.now();
1374
+ let consecutiveRateLimits = 0;
1375
+
1376
+ while (Date.now() - startTime < timeout) {
1377
+ try {
1378
+ const status = await this.getProof(qHash);
1379
+ consecutiveRateLimits = 0;
1380
+
1381
+ if (onProgress && typeof onProgress === 'function') {
1382
+ onProgress(status.data || status);
1383
+ }
1384
+
1385
+ const currentStatus = status.data?.status || status.status;
1386
+ if (this._isTerminalStatus(currentStatus)) {
1387
+ this._log('Verification completed', { status: currentStatus, duration: Date.now() - startTime });
1388
+ return status;
1389
+ }
1390
+
1391
+ await new Promise(resolve => setTimeout(resolve, interval));
1392
+
1393
+ } catch (error) {
1394
+ this._log('Status poll error', error.message);
1395
+ if (error instanceof ValidationError) {
1396
+ throw error;
1397
+ }
1398
+
1399
+ let nextDelay = interval;
1400
+ if (error instanceof ApiError && Number(error.statusCode) === 429) {
1401
+ consecutiveRateLimits += 1;
1402
+ const exp = Math.min(6, consecutiveRateLimits);
1403
+ const base = Math.max(500, Number(interval) || 0);
1404
+ const max = 30000;
1405
+ const backoff = Math.min(max, base * Math.pow(2, exp));
1406
+ const jitter = Math.floor(backoff * (0.5 + Math.random() * 0.5));
1407
+ nextDelay = jitter;
1408
+ }
1409
+
1410
+ await new Promise(resolve => setTimeout(resolve, nextDelay));
1411
+ }
1412
+ }
1413
+
1414
+ throw new NetworkError(`Polling timeout after ${timeout}ms`, 'POLLING_TIMEOUT');
1415
+ }
1416
+
1417
+ async detectChainId() {
1418
+ if (typeof window === 'undefined' || !window.ethereum) {
1419
+ throw new ConfigurationError('No Web3 wallet detected');
1420
+ }
1421
+
1422
+ try {
1423
+ const chainId = await window.ethereum.request({ method: 'eth_chainId' });
1424
+ return parseInt(chainId, 16);
1425
+ } catch (error) {
1426
+ throw new NetworkError(`Failed to detect chain ID: ${error.message}`);
1427
+ }
1428
+ }
1429
+
1430
+ async revokeOwnProof(qHash, wallet) {
1431
+ if (!qHash || typeof qHash !== 'string') {
1432
+ throw new ValidationError('qHash is required');
1433
+ }
1434
+ const providerWallet = wallet || this._getDefaultBrowserWallet();
1435
+ const { signerWalletAddress: address, provider } = await this._resolveWalletSigner(providerWallet);
1436
+ if (!address || typeof address !== 'string') {
1437
+ throw new ConfigurationError('No wallet accounts available');
1438
+ }
1439
+ const signerIsEvm = EVM_ADDRESS_RE.test(this._normalizeIdentity(address));
1440
+ const chain = this._inferChainForAddress(address);
1441
+ const signatureMethod = signerIsEvm ? 'eip191' : 'ed25519';
1442
+ const signedTimestamp = Date.now();
1443
+
1444
+ const message = constructVerificationMessage({
1445
+ walletAddress: address,
1446
+ signedTimestamp,
1447
+ data: { action: 'revoke_proof', qHash: qHash },
1448
+ verifierIds: ['ownership-basic'],
1449
+ ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
1450
+ });
1451
+
1452
+ let signature;
1453
+ try {
1454
+ signature = await signMessage({
1455
+ provider,
1456
+ message,
1457
+ walletAddress: address,
1458
+ ...(signerIsEvm ? {} : { chain })
1459
+ });
1460
+ } catch (error) {
1461
+ if (error.code === 4001) {
1462
+ throw new ValidationError('User rejected revocation signature');
1463
+ }
1464
+ throw new ValidationError(`Failed to sign revocation: ${error.message}`);
1465
+ }
1466
+
1467
+ const json = await this._makeRequest('POST', `/api/v1/proofs/revoke-self/${qHash}`, {
1468
+ walletAddress: address,
1469
+ signature,
1470
+ signedTimestamp,
1471
+ ...(signerIsEvm ? {} : { chain, signatureMethod })
1472
+ });
1473
+ if (!json.success) {
1474
+ throw new ApiError(json.error?.message || 'Failed to revoke proof', json.error);
1475
+ }
1476
+ return true;
1477
+ }
1478
+
1479
+ _buildProofsByWalletQuery(options = {}) {
1480
+ const qs = [];
1481
+ if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
1482
+ const cursorRaw = options.cursor !== null && options.cursor !== undefined ? String(options.cursor).trim() : '';
1483
+ if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
1484
+ else if (options.offset !== undefined && options.offset !== null) {
1485
+ qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
1486
+ }
1487
+ if (options.q) qs.push(`q=${encodeURIComponent(String(options.q))}`);
1488
+ if (options.qHash) qs.push(`qHash=${encodeURIComponent(String(options.qHash).toLowerCase())}`);
1489
+ if (options.verifierId) qs.push(`verifierId=${encodeURIComponent(String(options.verifierId))}`);
1490
+ if (options.verifierIds) qs.push(`verifierIds=${encodeURIComponent(String(options.verifierIds))}`);
1491
+ if (options.tags) qs.push(`tags=${encodeURIComponent(String(options.tags))}`);
1492
+ if (options.tagPrefix) qs.push(`tagPrefix=${encodeURIComponent(String(options.tagPrefix))}`);
1493
+ if (options.tagContains) qs.push(`tagContains=${encodeURIComponent(String(options.tagContains))}`);
1494
+ if (options.tagPrefixesAll) qs.push(`tagPrefixesAll=${encodeURIComponent(String(options.tagPrefixesAll))}`);
1495
+ if (options.status) qs.push(`status=${encodeURIComponent(String(options.status))}`);
1496
+ if (options.appId) qs.push(`appId=${encodeURIComponent(String(options.appId))}`);
1497
+ if (options.chainCoverage) qs.push(`chainCoverage=${encodeURIComponent(String(options.chainCoverage))}`);
1498
+ if (options.privacyLevel) qs.push(`privacyLevel=${encodeURIComponent(String(options.privacyLevel))}`);
1499
+ if (options.includeHistory) qs.push('includeHistory=1');
1500
+ if (options.includeFacets) qs.push(`includeFacets=${encodeURIComponent(String(options.includeFacets))}`);
1501
+ if (options.visibility) qs.push(`visibility=${encodeURIComponent(String(options.visibility))}`);
1502
+ if (options.isPublicRead) qs.push('isPublicRead=1');
1503
+ return qs;
1504
+ }
1505
+
1506
+ async getProofsByWallet(walletAddress, options = {}) {
1507
+ if (!walletAddress || typeof walletAddress !== 'string') {
1508
+ throw new ValidationError('walletAddress is required');
1509
+ }
1510
+
1511
+ const id = walletAddress.trim();
1512
+ const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
1513
+
1514
+ const qs = this._buildProofsByWalletQuery(options);
1515
+
1516
+ const query = qs.length ? `?${qs.join('&')}` : '';
1517
+ const response = await this._makeRequest(
1518
+ 'GET',
1519
+ `/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`
1520
+ );
1521
+
1522
+ if (!response.success) {
1523
+ throw new ApiError(`Failed to get proofs: ${response.error?.message || 'Unknown error'}`, response.error);
1524
+ }
1525
+
1526
+ const proofs = response.data?.proofs || response.data || response.proofs || [];
1527
+ return {
1528
+ success: true,
1529
+ proofs: Array.isArray(proofs) ? proofs : [],
1530
+ totalCount: typeof response.data?.totalCount === 'number' ? response.data.totalCount : null,
1531
+ hasMore: Boolean(response.data?.hasMore),
1532
+ nextOffset: response.data?.nextOffset ?? null,
1533
+ nextCursor:
1534
+ typeof response.data?.nextCursor === 'string' && response.data.nextCursor.trim()
1535
+ ? response.data.nextCursor.trim()
1536
+ : null,
1537
+ facets: response.data?.facets || null,
1538
+ };
1539
+ }
1540
+
1541
+ async getPrivateProofsByWallet(walletAddress, options = {}, wallet = null) {
1542
+ if (!walletAddress || typeof walletAddress !== 'string') {
1543
+ throw new ValidationError('walletAddress is required');
1544
+ }
1545
+
1546
+ const id = walletAddress.trim();
1547
+ const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
1548
+
1549
+ const requestedIdentity = this._normalizeIdentity(id);
1550
+
1551
+ if (!wallet) {
1552
+ const defaultWallet = this._getDefaultBrowserWallet();
1553
+ if (!defaultWallet) {
1554
+ throw new ConfigurationError('No wallet provider available');
1555
+ }
1556
+ wallet = defaultWallet;
1557
+ }
1558
+
1559
+ const { signerWalletAddress, provider } = await this._resolveWalletSigner(wallet);
1560
+
1561
+ if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
1562
+ throw new ConfigurationError('No wallet accounts available');
1563
+ }
1564
+
1565
+ const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
1566
+ if (!normalizedSigner || normalizedSigner !== requestedIdentity) {
1567
+ throw new ValidationError('wallet must match walletAddress for private proof access');
1568
+ }
1569
+
1570
+ const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
1571
+ const chain = this._inferChainForAddress(normalizedSigner, options?.chain);
1572
+ const signatureMethod = (typeof options?.signatureMethod === 'string' && options.signatureMethod.trim())
1573
+ ? options.signatureMethod.trim()
1574
+ : (signerIsEvm ? 'eip191' : 'ed25519');
1575
+
1576
+ const signedTimestamp = Date.now();
1577
+ const message = constructVerificationMessage({
1578
+ walletAddress: signerWalletAddress,
1579
+ signedTimestamp,
1580
+ data: { action: 'access_private_proofs_by_wallet', walletAddress: normalizedSigner },
1581
+ verifierIds: ['ownership-basic'],
1582
+ ...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
1583
+ });
1584
+
1585
+ let signature;
1586
+ try {
1587
+ signature = await signMessage({
1588
+ provider,
1589
+ message,
1590
+ walletAddress: signerWalletAddress,
1591
+ ...(signerIsEvm ? {} : { chain })
1592
+ });
1593
+ } catch (error) {
1594
+ if (error.code === 4001) {
1595
+ throw new ValidationError('User rejected signature request');
1596
+ }
1597
+ throw new ValidationError(`Failed to sign message: ${error.message}`);
1598
+ }
1599
+
1600
+ const qs = this._buildProofsByWalletQuery(options);
1601
+ const query = qs.length ? `?${qs.join('&')}` : '';
1602
+
1603
+ const response = await this._makeRequest('GET', `/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`, null, {
1604
+ 'x-wallet-address': signerWalletAddress,
1605
+ 'x-signature': signature,
1606
+ 'x-signed-timestamp': signedTimestamp.toString(),
1607
+ ...(signerIsEvm ? {} : { 'x-chain': chain, 'x-signature-method': signatureMethod })
1608
+ });
1609
+
1610
+ if (!response.success) {
1611
+ throw new ApiError(`Failed to get proofs: ${response.error?.message || 'Unauthorized'}`, response.error);
1612
+ }
1613
+
1614
+ const proofs = response.data?.proofs || response.data || response.proofs || [];
1615
+ return {
1616
+ success: true,
1617
+ proofs: Array.isArray(proofs) ? proofs : [],
1618
+ totalCount: typeof response.data?.totalCount === 'number' ? response.data.totalCount : null,
1619
+ hasMore: Boolean(response.data?.hasMore),
1620
+ nextOffset: response.data?.nextOffset ?? null,
1621
+ nextCursor:
1622
+ typeof response.data?.nextCursor === 'string' && response.data.nextCursor.trim()
1623
+ ? response.data.nextCursor.trim()
1624
+ : null
1625
+ };
1626
+ }
1627
+
1628
+ async gateCheck(params = {}) {
1629
+ const address = (params.address || '').toString();
1630
+ if (!validateUniversalAddress(address, params.chain)) {
1631
+ throw new ValidationError('Valid address is required');
1632
+ }
1633
+
1634
+ const gateIdParam = typeof params.gateId === 'string' ? params.gateId.trim() : '';
1635
+ const verifierIds = gateIdParam ? undefined : params.verifierIds;
1636
+
1637
+ const qs = new URLSearchParams();
1638
+ qs.set('address', address);
1639
+
1640
+ const setIfPresent = (key, value) => {
1641
+ if (value === undefined || value === null) return;
1642
+ const str = typeof value === 'string' ? value : String(value);
1643
+ if (str.length === 0) return;
1644
+ qs.set(key, str);
1645
+ };
1646
+
1647
+ const setBoolIfPresent = (key, value) => {
1648
+ if (value === undefined || value === null) return;
1649
+ qs.set(key, value ? 'true' : 'false');
1650
+ };
1651
+
1652
+ const setCsvIfPresent = (key, value) => {
1653
+ if (value === undefined || value === null) return;
1654
+ if (Array.isArray(value)) {
1655
+ const items = value.map(v => String(v).trim()).filter(Boolean);
1656
+ if (items.length) qs.set(key, items.join(','));
1657
+ return;
1658
+ }
1659
+ setIfPresent(key, value);
1660
+ };
1661
+
1662
+ setIfPresent('gateId', gateIdParam);
1663
+ setCsvIfPresent('verifierIds', verifierIds);
1664
+ setBoolIfPresent('requireAll', params.requireAll);
1665
+ setIfPresent('minCount', params.minCount);
1666
+ setIfPresent('sinceDays', params.sinceDays);
1667
+ setIfPresent('since', params.since);
1668
+ setIfPresent('limit', params.limit);
1669
+ setBoolIfPresent('includePrivate', params.includePrivate);
1670
+ setBoolIfPresent('includeQHashes', params.includeQHashes);
1671
+
1672
+ setIfPresent('referenceType', params.referenceType);
1673
+ setIfPresent('referenceId', params.referenceId);
1674
+ setIfPresent('tag', params.tag);
1675
+ setCsvIfPresent('tags', params.tags);
1676
+ setIfPresent('contentType', params.contentType);
1677
+ setIfPresent('content', params.content);
1678
+ setIfPresent('contentHash', params.contentHash);
1679
+
1680
+ setIfPresent('contractAddress', params.contractAddress);
1681
+ setIfPresent('tokenId', params.tokenId);
1682
+ setIfPresent('chainId', params.chainId);
1683
+ setIfPresent('domain', params.domain);
1684
+ setIfPresent('minBalance', params.minBalance);
1685
+
1686
+ setIfPresent('provider', params.provider);
1687
+ setIfPresent('handle', params.handle);
1688
+ setIfPresent('namespace', params.namespace);
1689
+ setIfPresent('ownerAddress', params.ownerAddress);
1690
+ setIfPresent('riskLevel', params.riskLevel);
1691
+ setBoolIfPresent('sanctioned', params.sanctioned);
1692
+ setBoolIfPresent('poisoned', params.poisoned);
1693
+ setIfPresent('primaryWalletAddress', params.primaryWalletAddress);
1694
+ setIfPresent('secondaryWalletAddress', params.secondaryWalletAddress);
1695
+ setIfPresent('verificationMethod', params.verificationMethod);
1696
+ setIfPresent('neusPersonhoodId', params.neusPersonhoodId);
1697
+
1698
+ let headersOverride = null;
1699
+ if (params.includePrivate === true) {
1700
+ const provided = params.privateAuth && typeof params.privateAuth === 'object' ? params.privateAuth : null;
1701
+ let auth = provided;
1702
+ if (!auth) {
1703
+ const walletCandidate = params.wallet || this._getDefaultBrowserWallet();
1704
+ if (walletCandidate) {
1705
+ auth = await this._buildPrivateGateAuth({
1706
+ address,
1707
+ wallet: walletCandidate,
1708
+ chain: params.chain,
1709
+ signatureMethod: params.signatureMethod
1710
+ });
1711
+ }
1712
+ }
1713
+ if (auth) {
1714
+ const normalizedAuthWallet = this._normalizeIdentity(auth.walletAddress);
1715
+ const normalizedAddress = this._normalizeIdentity(address);
1716
+ if (!normalizedAuthWallet || normalizedAuthWallet !== normalizedAddress) {
1717
+ throw new ValidationError('privateAuth.walletAddress must match address when includePrivate=true');
1718
+ }
1719
+ const authChain = (typeof auth.chain === 'string' && auth.chain.includes(':')) ? auth.chain.trim() : null;
1720
+ const authSignatureMethod = (typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim())
1721
+ ? auth.signatureMethod.trim()
1722
+ : null;
1723
+ headersOverride = {
1724
+ 'x-wallet-address': String(auth.walletAddress),
1725
+ 'x-signature': String(auth.signature),
1726
+ 'x-signed-timestamp': String(auth.signedTimestamp),
1727
+ ...(authChain ? { 'x-chain': authChain } : {}),
1728
+ ...(authSignatureMethod ? { 'x-signature-method': authSignatureMethod } : {})
1729
+ };
1730
+ }
1731
+ }
1732
+
1733
+ let mergedHeaders = headersOverride;
1734
+ if (!mergedHeaders && !gateIdParam) {
1735
+ try {
1736
+ const sponsorHeaders = await this._resolveSponsorGrantHeaders(
1737
+ Array.isArray(verifierIds)
1738
+ ? verifierIds
1739
+ : (verifierIds ? [verifierIds] : [])
1740
+ );
1741
+ if (sponsorHeaders && Object.keys(sponsorHeaders).length > 0) {
1742
+ mergedHeaders = sponsorHeaders;
1743
+ }
1744
+ } catch (error) {
1745
+ this._log('Sponsor grant unavailable for gateCheck (continuing without)', error?.message || String(error));
1746
+ }
1747
+ }
1748
+
1749
+ const response = await this._makeRequest('GET', `/api/v1/proofs/check?${qs.toString()}`, null, mergedHeaders);
1750
+ if (!response.success) {
1751
+ throw new ApiError(`Gate check failed: ${response.error?.message || 'Unknown error'}`, response.error);
1752
+ }
1753
+ return response;
1754
+ }
1755
+
1756
+ /**
1757
+ * Get the public snapshot of a published gate: requirements, charge, schedule,
1758
+ * checkout plan, and reward presence. Never returns the secret reward value —
1759
+ * that is delivered post-verify via fulfillGate().
1760
+ *
1761
+ * @param {string} gateId Published gate handle
1762
+ * @returns {Promise<object>} Public gate snapshot
1763
+ */
1764
+ async getGate(gateId) {
1765
+ const id = String(gateId || '').trim();
1766
+ if (!id || id.length > 80 || !/^[a-zA-Z0-9:_-]+$/.test(id)) {
1767
+ throw new ValidationError('Valid gateId is required');
1768
+ }
1769
+ const response = await this._makeRequest('GET', `/api/v1/profile/gates/${encodeURIComponent(id)}`);
1770
+ if (!response.success || !response.data?.gate) {
1771
+ throw new ApiError(`Gate lookup failed: ${response.error?.message || 'Gate not found'}`, response.error);
1772
+ }
1773
+ return response.data.gate;
1774
+ }
1775
+
1776
+ /**
1777
+ * Post-verify reward delivery for hosted gate checkout. Requires a verified
1778
+ * proof (qHash) for the gate; paid gates also require payment evidence
1779
+ * (paymentCheckoutSessionId for card, or paymentTxHash for USDC).
1780
+ *
1781
+ * @param {object} params
1782
+ * @param {string} params.gateId Published gate handle
1783
+ * @param {string} params.qHash Verified proof receipt id
1784
+ * @param {string} [params.walletAddress] Wallet bound to the proof (required without a session cookie)
1785
+ * @param {string} [params.paymentCheckoutSessionId] Stripe checkout session id (card rail)
1786
+ * @param {string} [params.paymentTxHash] USDC payment transaction hash (wallet rail)
1787
+ * @returns {Promise<object>} `{ success, data: { gateId, qHash, fulfillment, successReturnUrl? } }`
1788
+ */
1789
+ async fulfillGate(params = {}) {
1790
+ const gateId = String(params.gateId || '').trim();
1791
+ if (!gateId || gateId.length > 80 || !/^[a-zA-Z0-9:_-]+$/.test(gateId)) {
1792
+ throw new ValidationError('Valid gateId is required');
1793
+ }
1794
+ const qHash = String(params.qHash || '').trim();
1795
+ if (!/^0x[a-fA-F0-9]{64}$/.test(qHash)) {
1796
+ throw new ValidationError('Valid qHash is required');
1797
+ }
1798
+ const body = { qHash };
1799
+ const walletAddress = String(params.walletAddress || '').trim();
1800
+ if (walletAddress) body.walletAddress = walletAddress;
1801
+ const paymentCheckoutSessionId = String(params.paymentCheckoutSessionId || '').trim();
1802
+ if (paymentCheckoutSessionId) body.paymentCheckoutSessionId = paymentCheckoutSessionId;
1803
+ const paymentTxHash = String(params.paymentTxHash || '').trim();
1804
+ if (paymentTxHash) body.paymentTxHash = paymentTxHash;
1805
+
1806
+ const response = await this._makeRequest(
1807
+ 'POST',
1808
+ `/api/v1/profile/gates/${encodeURIComponent(gateId)}/fulfill`,
1809
+ body
1810
+ );
1811
+ if (!response.success) {
1812
+ throw new ApiError(`Gate fulfillment failed: ${response.error?.message || 'Unknown error'}`, response.error);
1813
+ }
1814
+ return response;
1815
+ }
1816
+
1817
+ async checkGate(params) {
1818
+ const { walletAddress, requirements, proofs: preloadedProofs } = params;
1819
+
1820
+ if (!validateUniversalAddress(walletAddress)) {
1821
+ throw new ValidationError('Valid walletAddress is required');
1822
+ }
1823
+ if (!Array.isArray(requirements) || requirements.length === 0) {
1824
+ throw new ValidationError('requirements array is required and must not be empty');
1825
+ }
1826
+
1827
+ let proofs = preloadedProofs;
1828
+ if (!proofs) {
1829
+ const result = await this.getProofsByWallet(walletAddress, { limit: 100 });
1830
+ proofs = result.proofs;
1831
+ }
1832
+
1833
+ const now = Date.now();
1834
+ const existing = {};
1835
+ const missing = [];
1836
+
1837
+ for (const req of requirements) {
1838
+ const { verifierId, maxAgeMs, optional = false, minCount = 1, match } = req;
1839
+
1840
+ const matchingProofs = (proofs || []).filter(proof => {
1841
+ const verifiedVerifiers = proof.verifiedVerifiers || [];
1842
+ const verifier = verifiedVerifiers.find(
1843
+ v => v.verifierId === verifierId && v.verified === true
1844
+ );
1845
+ if (!verifier) return false;
1846
+
1847
+ if (proof.revokedAt) return false;
1848
+
1849
+ if (maxAgeMs) {
1850
+ const proofTimestamp = proof.createdAt || proof.signedTimestamp || 0;
1851
+ const proofAge = now - proofTimestamp;
1852
+ if (proofAge > maxAgeMs) return false;
1853
+ }
1854
+
1855
+ if (match && typeof match === 'object') {
1856
+ const data = verifier.data || {};
1857
+ const input = data.input || {};
1858
+ const matchObj = Array.isArray(match)
1859
+ ? Object.fromEntries(
1860
+ match
1861
+ .filter((m) => m && m.path && String(m.value ?? '').trim() !== '')
1862
+ .map((m) => [String(m.path).trim(), m.value])
1863
+ )
1864
+ : match;
1865
+
1866
+ for (const [key, expected] of Object.entries(matchObj)) {
1867
+ let actualValue = null;
1868
+
1869
+ if (key.includes('.')) {
1870
+ const parts = key.split('.');
1871
+ let current = data;
1872
+
1873
+ if (parts[0] === 'input' && input) {
1874
+ current = input;
1875
+ parts.shift();
1876
+ }
1877
+
1878
+ for (const part of parts) {
1879
+ if (current && typeof current === 'object' && part in current) {
1880
+ current = current[part];
1881
+ } else {
1882
+ current = undefined;
1883
+ break;
1884
+ }
1885
+ }
1886
+ actualValue = current;
1887
+ } else {
1888
+ actualValue = input[key] || data[key];
1889
+ }
1890
+
1891
+ if (key === 'content' && actualValue === undefined) {
1892
+ actualValue = data.reference?.id || data.content;
1893
+ }
1894
+
1895
+ if (actualValue === undefined) {
1896
+ const claims = data.claims || {};
1897
+ if (key === 'contractAddress') {
1898
+ actualValue = input.contractAddress || data.contractAddress;
1899
+ } else if (key === 'tokenId') {
1900
+ actualValue = input.tokenId || data.tokenId;
1901
+ } else if (key === 'chainId') {
1902
+ actualValue = input.chainId || data.chainId;
1903
+ } else if (key === 'ownerAddress') {
1904
+ actualValue = input.ownerAddress || data.owner || data.walletAddress;
1905
+ } else if (key === 'minBalance') {
1906
+ actualValue = input.minBalance || data.onChainData?.requiredMinBalance || data.minBalance;
1907
+ } else if (key === 'primaryWalletAddress') {
1908
+ actualValue = data.primaryWalletAddress;
1909
+ } else if (key === 'secondaryWalletAddress') {
1910
+ actualValue = data.secondaryWalletAddress;
1911
+ } else if (key === 'verificationMethod') {
1912
+ actualValue = data.verificationMethod;
1913
+ } else if (key === 'domain') {
1914
+ actualValue = data.domain;
1915
+ } else if (key === 'handle') {
1916
+ actualValue = data.handle || data.pseudonymId;
1917
+ } else if (key === 'namespace') {
1918
+ actualValue =
1919
+ data.namespace !== undefined && data.namespace !== null && data.namespace !== ''
1920
+ ? data.namespace
1921
+ : 'neus';
1922
+ } else if (key === 'claims.sanctions_passed') {
1923
+ actualValue = claims.sanctions_passed ?? claims.sanctionsPassed;
1924
+ } else if (key === 'claims.age_min') {
1925
+ actualValue = claims.age_min ?? claims.ageMin;
1926
+ } else if (key === 'neusPersonhoodId') {
1927
+ actualValue = data.neusPersonhoodId;
1928
+ } else if (key === 'riskLevel') {
1929
+ actualValue = data.riskLevel;
1930
+ } else if (key === 'sanctioned') {
1931
+ actualValue = data.sanctioned;
1932
+ } else if (key === 'poisoned') {
1933
+ actualValue = data.poisoned;
1934
+ }
1935
+ }
1936
+
1937
+ if (key === 'contentHash' && actualValue === undefined && data.content) {
1938
+ try {
1939
+ let hash = 0;
1940
+ const str = String(data.content);
1941
+ for (let i = 0; i < str.length; i++) {
1942
+ const char = str.charCodeAt(i);
1943
+ hash = ((hash << 5) - hash) + char;
1944
+ hash = hash & hash;
1945
+ }
1946
+ actualValue = `0x${ Math.abs(hash).toString(16).padStart(64, '0').substring(0, 66)}`;
1947
+ } catch {
1948
+ actualValue = String(data.content);
1949
+ }
1950
+ }
1951
+
1952
+ let normalizedActual = actualValue;
1953
+ let normalizedExpected = expected;
1954
+
1955
+ if (actualValue === undefined || actualValue === null) {
1956
+ return false;
1957
+ }
1958
+
1959
+ if (typeof actualValue === 'boolean' || (key && (key.includes('sanctions') || key.includes('sanctioned') || key.includes('poisoned')))) {
1960
+ const bActual = Boolean(actualValue);
1961
+ const bExpected = expected === true || expected === 'true' || String(expected).toLowerCase() === 'true';
1962
+ if (bActual !== bExpected) return false;
1963
+ continue;
1964
+ }
1965
+
1966
+ if (key === 'chainId' || (key === 'tokenId' && (typeof actualValue === 'number' || !isNaN(Number(actualValue))))) {
1967
+ normalizedActual = Number(actualValue);
1968
+ normalizedExpected = Number(expected);
1969
+ if (isNaN(normalizedActual) || isNaN(normalizedExpected)) return false;
1970
+ }
1971
+ else if (typeof actualValue === 'string' && (actualValue.startsWith('0x') || actualValue.length > 20)) {
1972
+ normalizedActual = actualValue.toLowerCase();
1973
+ normalizedExpected = typeof expected === 'string' ? String(expected).toLowerCase() : expected;
1974
+ }
1975
+ else {
1976
+ normalizedActual = actualValue;
1977
+ normalizedExpected = expected;
1978
+ }
1979
+
1980
+ if (normalizedActual !== normalizedExpected) {
1981
+ return false;
1982
+ }
1983
+ }
1984
+ }
1985
+
1986
+ return true;
1987
+ });
1988
+
1989
+ if (matchingProofs.length >= minCount) {
1990
+ const sorted = matchingProofs.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
1991
+ existing[verifierId] = sorted[0];
1992
+ } else if (!optional) {
1993
+ missing.push(req);
1994
+ }
1995
+ }
1996
+
1997
+ return {
1998
+ satisfied: missing.length === 0,
1999
+ missing,
2000
+ existing,
2001
+ allProofs: proofs
2002
+ };
2003
+ }
2004
+
2005
+ async _getWalletAddress() {
2006
+ if (typeof window === 'undefined' || !window.ethereum) {
2007
+ throw new ConfigurationError('No Web3 wallet detected');
2008
+ }
2009
+
2010
+ const accounts = await window.ethereum.request({ method: 'eth_accounts' });
2011
+ if (!accounts || accounts.length === 0) {
2012
+ throw new ConfigurationError('No wallet accounts available');
2013
+ }
2014
+
2015
+ return accounts[0];
2016
+ }
2017
+
2018
+ async _makeRequest(method, endpoint, data = null, headersOverride = null) {
2019
+ const url = `${this.baseUrl}${endpoint}`;
2020
+
2021
+ const controller = new AbortController();
2022
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
2023
+
2024
+ const options = {
2025
+ method,
2026
+ headers: { ...this.defaultHeaders, ...(headersOverride || {}) },
2027
+ signal: controller.signal
2028
+ };
2029
+
2030
+ if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
2031
+ options.body = JSON.stringify(data);
2032
+ }
2033
+
2034
+ this._log(`${method} ${endpoint}`, data ? { requestBodyKeys: Object.keys(data) } : {});
2035
+
2036
+ try {
2037
+ const response = await fetch(url, options);
2038
+ clearTimeout(timeoutId);
2039
+
2040
+ let responseData;
2041
+ try {
2042
+ responseData = await response.json();
2043
+ } catch {
2044
+ responseData = { error: { message: 'Invalid JSON response' } };
2045
+ }
2046
+
2047
+ if (!response.ok) {
2048
+ throw ApiError.fromResponse(response, responseData);
2049
+ }
2050
+
2051
+ return responseData;
2052
+
2053
+ } catch (error) {
2054
+ clearTimeout(timeoutId);
2055
+
2056
+ if (error.name === 'AbortError') {
2057
+ throw new NetworkError(`Request timeout after ${this.config.timeout}ms`);
2058
+ }
2059
+
2060
+ if (error instanceof ApiError) {
2061
+ throw error;
2062
+ }
2063
+
2064
+ throw new NetworkError(`Network error: ${error.message}`);
2065
+ }
2066
+ }
2067
+
2068
+ _formatResponse(response) {
2069
+ const qHash = response?.data?.qHash ||
2070
+ response?.qHash ||
2071
+ response?.data?.resource?.qHash ||
2072
+ response?.data?.id;
2073
+
2074
+ const status = response?.data?.status ||
2075
+ response?.status ||
2076
+ response?.data?.resource?.status ||
2077
+ (response?.success ? 'completed' : 'unknown');
2078
+
2079
+ return {
2080
+ success: response.success,
2081
+ qHash,
2082
+ status,
2083
+ data: response.data,
2084
+ message: response.message,
2085
+ timestamp: Date.now(),
2086
+ proofUrl: qHash ? `${this.baseUrl}/api/v1/proofs/${qHash}` : null
2087
+ };
2088
+ }
2089
+
2090
+ _isTerminalStatus(status) {
2091
+ const terminalStates = [
2092
+ 'verified',
2093
+ 'verified_crosschain_propagated',
2094
+ 'completed_all_successful',
2095
+ 'failed',
2096
+ 'error',
2097
+ 'rejected',
2098
+ 'cancelled'
2099
+ ];
2100
+ return typeof status === 'string' && terminalStates.some(state => status.includes(state));
2101
+ }
2102
+
2103
+ _log(message, data = {}) {
2104
+ if (this.config.enableLogging) {
2105
+ try {
2106
+ const prefix = '[neus/sdk]';
2107
+ if (data && Object.keys(data).length > 0) {
2108
+ // eslint-disable-next-line no-console -- gated debug logging
2109
+ console.log(prefix, message, data);
2110
+ } else {
2111
+ // eslint-disable-next-line no-console -- gated debug logging
2112
+ console.log(prefix, message);
2113
+ }
2114
+ } catch {
2115
+ void 0;
2116
+ }
2117
+ }
2118
+ }
2119
+ }
2120
+
2121
+ export { PORTABLE_PROOF_SIGNER_HEADER, constructVerificationMessage };