@neus/sdk 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -112
- package/cjs/client.cjs +545 -175
- package/cjs/errors.cjs +1 -0
- package/cjs/gates.cjs +1 -0
- package/cjs/index.cjs +725 -180
- package/cjs/utils.cjs +338 -3
- package/client.js +1951 -1729
- package/index.js +75 -64
- package/neus-logo.svg +3 -0
- package/package.json +9 -5
- package/types.d.ts +215 -30
- package/utils.js +407 -4
- package/widgets/README.md +64 -53
- package/widgets/index.js +9 -9
- package/widgets/verify-gate/dist/ProofBadge.js +51 -38
- package/widgets/verify-gate/dist/VerifyGate.js +283 -58
- package/widgets/verify-gate/index.js +13 -13
package/utils.js
CHANGED
|
@@ -5,6 +5,57 @@
|
|
|
5
5
|
|
|
6
6
|
import { SDKError, ApiError, ValidationError } from './errors.js';
|
|
7
7
|
|
|
8
|
+
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
9
|
+
|
|
10
|
+
function encodeBase58Bytes(input) {
|
|
11
|
+
let source;
|
|
12
|
+
if (input instanceof Uint8Array) {
|
|
13
|
+
source = input;
|
|
14
|
+
} else if (input instanceof ArrayBuffer) {
|
|
15
|
+
source = new Uint8Array(input);
|
|
16
|
+
} else if (ArrayBuffer.isView(input)) {
|
|
17
|
+
source = new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
18
|
+
} else if (typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(input)) {
|
|
19
|
+
source = new Uint8Array(input);
|
|
20
|
+
} else {
|
|
21
|
+
throw new SDKError('Unsupported non-EVM signature byte format', 'INVALID_SIGNATURE_FORMAT');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (source.length === 0) return '';
|
|
25
|
+
|
|
26
|
+
let zeroes = 0;
|
|
27
|
+
while (zeroes < source.length && source[zeroes] === 0) {
|
|
28
|
+
zeroes++;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const iFactor = Math.log(256) / Math.log(58);
|
|
32
|
+
const size = (((source.length - zeroes) * iFactor) + 1) >>> 0;
|
|
33
|
+
const b58 = new Uint8Array(size);
|
|
34
|
+
|
|
35
|
+
let length = 0;
|
|
36
|
+
for (let i = zeroes; i < source.length; i++) {
|
|
37
|
+
let carry = source[i];
|
|
38
|
+
let j = 0;
|
|
39
|
+
for (let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {
|
|
40
|
+
carry += 256 * b58[k];
|
|
41
|
+
b58[k] = carry % 58;
|
|
42
|
+
carry = (carry / 58) | 0;
|
|
43
|
+
}
|
|
44
|
+
length = j;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let it = size - length;
|
|
48
|
+
while (it < size && b58[it] === 0) {
|
|
49
|
+
it++;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let out = BASE58_ALPHABET[0].repeat(zeroes);
|
|
53
|
+
for (; it < size; it++) {
|
|
54
|
+
out += BASE58_ALPHABET[b58[it]];
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
8
59
|
/**
|
|
9
60
|
* Deterministic JSON stringification for consistent serialization
|
|
10
61
|
* @param {Object} obj - Object to stringify
|
|
@@ -108,6 +159,41 @@ export function validateWalletAddress(address) {
|
|
|
108
159
|
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
109
160
|
}
|
|
110
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Validate universal wallet address format.
|
|
164
|
+
* Uses chain namespace when provided; otherwise applies conservative multi-chain checks.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} address - Address to validate
|
|
167
|
+
* @param {string} [chain] - Optional CAIP-2 chain reference (namespace:reference)
|
|
168
|
+
* @returns {boolean} True if valid universal wallet address
|
|
169
|
+
*/
|
|
170
|
+
export function validateUniversalAddress(address, chain) {
|
|
171
|
+
if (!address || typeof address !== 'string') return false;
|
|
172
|
+
const value = address.trim();
|
|
173
|
+
if (!value) return false;
|
|
174
|
+
|
|
175
|
+
const chainRef = typeof chain === 'string' ? chain.trim().toLowerCase() : '';
|
|
176
|
+
const namespace = chainRef.includes(':') ? chainRef.split(':')[0] : '';
|
|
177
|
+
|
|
178
|
+
if (validateWalletAddress(value)) return true;
|
|
179
|
+
if (namespace === 'eip155') return false;
|
|
180
|
+
|
|
181
|
+
if (namespace === 'solana') {
|
|
182
|
+
return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (namespace === 'bip122') {
|
|
186
|
+
return /^(bc1|tb1|bcrt1)[a-z0-9]{11,87}$/.test(value.toLowerCase()) || /^[13mn2][a-km-zA-HJ-NP-Z1-9]{25,62}$/.test(value);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (namespace === 'near') {
|
|
190
|
+
return /^[a-z0-9._-]{2,64}$/.test(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Generic fallback for universal-address style identifiers.
|
|
194
|
+
return /^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$/.test(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
111
197
|
/**
|
|
112
198
|
* Validate timestamp freshness
|
|
113
199
|
*
|
|
@@ -151,7 +237,7 @@ export function createVerificationData(content, owner, reference = null) {
|
|
|
151
237
|
|
|
152
238
|
return {
|
|
153
239
|
content,
|
|
154
|
-
owner: owner.toLowerCase(),
|
|
240
|
+
owner: validateWalletAddress(owner) ? owner.toLowerCase() : owner,
|
|
155
241
|
reference: reference || {
|
|
156
242
|
// Must be a valid backend enum value; 'content' is not supported.
|
|
157
243
|
type: 'other',
|
|
@@ -179,11 +265,328 @@ export function deriveDid(address, chainIdOrChain) {
|
|
|
179
265
|
} else {
|
|
180
266
|
if (typeof chainContext !== 'number') {
|
|
181
267
|
throw new SDKError('deriveDid: chainId (number) or chain (namespace:reference string) is required', 'INVALID_ARGUMENT');
|
|
182
|
-
|
|
268
|
+
}
|
|
183
269
|
return `did:pkh:eip155:${chainContext}:${address.toLowerCase()}`;
|
|
184
270
|
}
|
|
185
271
|
}
|
|
186
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Resolve DID from wallet identity via API endpoint
|
|
275
|
+
*
|
|
276
|
+
* @param {Object} params - DID resolution parameters
|
|
277
|
+
* @param {string} params.walletAddress - Wallet address to resolve
|
|
278
|
+
* @param {number} [params.chainId] - EVM chain ID
|
|
279
|
+
* @param {string} [params.chain] - Universal chain context (namespace:reference)
|
|
280
|
+
* @param {Object} [options] - Request options
|
|
281
|
+
* @param {string} [options.endpoint='/api/v1/profile/did/resolve'] - DID resolve endpoint
|
|
282
|
+
* @param {string} [options.apiUrl] - Absolute API base URL for non-relative endpoints
|
|
283
|
+
* @param {RequestCredentials} [options.credentials] - Fetch credentials mode
|
|
284
|
+
* @param {Record<string, string>} [options.headers] - Extra request headers
|
|
285
|
+
* @returns {Promise<{did: string, data: any, raw: any}>}
|
|
286
|
+
*/
|
|
287
|
+
export async function resolveDID(params, options = {}) {
|
|
288
|
+
const endpointPath = options.endpoint || '/api/v1/profile/did/resolve';
|
|
289
|
+
const apiUrl = typeof options.apiUrl === 'string' ? options.apiUrl.trim() : '';
|
|
290
|
+
|
|
291
|
+
const resolveEndpoint = (path) => {
|
|
292
|
+
if (!path || typeof path !== 'string') return null;
|
|
293
|
+
const trimmedPath = path.trim();
|
|
294
|
+
if (!trimmedPath) return null;
|
|
295
|
+
if (/^https?:\/\//i.test(trimmedPath)) return trimmedPath;
|
|
296
|
+
if (trimmedPath.startsWith('/')) {
|
|
297
|
+
if (!apiUrl) return trimmedPath;
|
|
298
|
+
try {
|
|
299
|
+
return new URL(trimmedPath, apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`).toString();
|
|
300
|
+
} catch {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const base = apiUrl || NEUS_CONSTANTS.API_BASE_URL;
|
|
305
|
+
if (!base || typeof base !== 'string') return null;
|
|
306
|
+
try {
|
|
307
|
+
return new URL(trimmedPath, base.endsWith('/') ? base : `${base}/`).toString();
|
|
308
|
+
} catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const endpoint = resolveEndpoint(endpointPath);
|
|
314
|
+
if (!endpoint) {
|
|
315
|
+
throw new SDKError('resolveDID requires a valid endpoint', 'INVALID_ENDPOINT');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const payload = {
|
|
319
|
+
walletAddress: params?.walletAddress,
|
|
320
|
+
chainId: params?.chainId,
|
|
321
|
+
chain: params?.chain
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const isRelative = endpoint.startsWith('/') || !/^https?:\/\//i.test(endpoint);
|
|
325
|
+
const credentialsMode = options.credentials !== undefined
|
|
326
|
+
? options.credentials
|
|
327
|
+
: (isRelative ? 'same-origin' : 'omit');
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const response = await fetch(endpoint, {
|
|
331
|
+
method: 'POST',
|
|
332
|
+
headers: {
|
|
333
|
+
'Content-Type': 'application/json',
|
|
334
|
+
Accept: 'application/json',
|
|
335
|
+
...(options.headers || {})
|
|
336
|
+
},
|
|
337
|
+
body: JSON.stringify(payload),
|
|
338
|
+
credentials: credentialsMode
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const json = await response.json().catch(() => null);
|
|
342
|
+
|
|
343
|
+
if (!response.ok) {
|
|
344
|
+
const msg = json?.error?.message || json?.error || json?.message || 'DID resolution failed';
|
|
345
|
+
throw new SDKError(msg, 'DID_RESOLVE_FAILED', json);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const did = json?.data?.did || json?.did;
|
|
349
|
+
if (!did || typeof did !== 'string') {
|
|
350
|
+
throw new SDKError('DID resolution missing DID', 'DID_RESOLVE_MISSING', json);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return { did, data: json?.data || null, raw: json };
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if (error instanceof SDKError) throw error;
|
|
356
|
+
throw new SDKError(`DID resolution failed: ${error?.message || error}`, 'DID_RESOLVE_FAILED');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Standardize verification request via backend signer-string endpoint
|
|
362
|
+
*
|
|
363
|
+
* @param {Object} params - Verification request payload
|
|
364
|
+
* @param {Object} [options] - Request options
|
|
365
|
+
* @param {string} [options.endpoint='/api/v1/verification/standardize'] - Standardize endpoint
|
|
366
|
+
* @param {string} [options.apiUrl] - Absolute API base URL for non-relative endpoints
|
|
367
|
+
* @param {RequestCredentials} [options.credentials] - Fetch credentials mode
|
|
368
|
+
* @param {Record<string, string>} [options.headers] - Extra request headers
|
|
369
|
+
* @returns {Promise<any>}
|
|
370
|
+
*/
|
|
371
|
+
export async function standardizeVerificationRequest(params, options = {}) {
|
|
372
|
+
const endpointPath = options.endpoint || '/api/v1/verification/standardize';
|
|
373
|
+
const apiUrl = typeof options.apiUrl === 'string' ? options.apiUrl.trim() : '';
|
|
374
|
+
|
|
375
|
+
const resolveEndpoint = (path) => {
|
|
376
|
+
if (!path || typeof path !== 'string') return null;
|
|
377
|
+
const trimmedPath = path.trim();
|
|
378
|
+
if (!trimmedPath) return null;
|
|
379
|
+
if (/^https?:\/\//i.test(trimmedPath)) return trimmedPath;
|
|
380
|
+
if (trimmedPath.startsWith('/')) {
|
|
381
|
+
if (!apiUrl) return trimmedPath;
|
|
382
|
+
try {
|
|
383
|
+
return new URL(trimmedPath, apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`).toString();
|
|
384
|
+
} catch {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const base = apiUrl || NEUS_CONSTANTS.API_BASE_URL;
|
|
389
|
+
if (!base || typeof base !== 'string') return null;
|
|
390
|
+
try {
|
|
391
|
+
return new URL(trimmedPath, base.endsWith('/') ? base : `${base}/`).toString();
|
|
392
|
+
} catch {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const endpoint = resolveEndpoint(endpointPath);
|
|
398
|
+
if (!endpoint) {
|
|
399
|
+
throw new SDKError('standardizeVerificationRequest requires a valid endpoint', 'INVALID_ENDPOINT');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const isRelative = endpoint.startsWith('/') || !/^https?:\/\//i.test(endpoint);
|
|
403
|
+
const credentialsMode = options.credentials !== undefined
|
|
404
|
+
? options.credentials
|
|
405
|
+
: (isRelative ? 'same-origin' : 'omit');
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
const response = await fetch(endpoint, {
|
|
409
|
+
method: 'POST',
|
|
410
|
+
headers: {
|
|
411
|
+
'Content-Type': 'application/json',
|
|
412
|
+
Accept: 'application/json',
|
|
413
|
+
...(options.headers || {})
|
|
414
|
+
},
|
|
415
|
+
body: JSON.stringify(params || {}),
|
|
416
|
+
credentials: credentialsMode
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const json = await response.json().catch(() => null);
|
|
420
|
+
if (!response.ok) {
|
|
421
|
+
const msg = json?.error?.message || json?.error || json?.message || 'Standardize request failed';
|
|
422
|
+
throw new SDKError(msg, 'STANDARDIZE_FAILED', json);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return json?.data || json;
|
|
426
|
+
} catch (error) {
|
|
427
|
+
if (error instanceof SDKError) throw error;
|
|
428
|
+
throw new SDKError(`Standardize request failed: ${error?.message || error}`, 'STANDARDIZE_FAILED');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Resolve default ZK Passport configuration values.
|
|
434
|
+
* Kept as an SDK utility to preserve existing app integrations.
|
|
435
|
+
*
|
|
436
|
+
* @param {Object} [overrides] - Caller-provided config overrides
|
|
437
|
+
* @returns {Object}
|
|
438
|
+
*/
|
|
439
|
+
export function resolveZkPassportConfig(overrides = {}) {
|
|
440
|
+
const defaults = {
|
|
441
|
+
provider: 'zkpassport',
|
|
442
|
+
scope: 'basic_kyc',
|
|
443
|
+
checkSanctions: true,
|
|
444
|
+
requireFaceMatch: true,
|
|
445
|
+
faceMatchMode: 'strict'
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
...defaults,
|
|
450
|
+
...(overrides && typeof overrides === 'object' ? overrides : {})
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Convert a UTF-8 string to `0x`-prefixed hex.
|
|
456
|
+
*
|
|
457
|
+
* @param {string} value
|
|
458
|
+
* @returns {string}
|
|
459
|
+
*/
|
|
460
|
+
export function toHexUtf8(value) {
|
|
461
|
+
const input = typeof value === 'string' ? value : String(value || '');
|
|
462
|
+
const bytes = new TextEncoder().encode(input);
|
|
463
|
+
return `0x${Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')}`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Sign an arbitrary message with the provided wallet/provider.
|
|
468
|
+
* Supports EIP-1193 wallets and signer-like objects.
|
|
469
|
+
*
|
|
470
|
+
* @param {Object} params
|
|
471
|
+
* @param {Object} [params.provider] - Wallet provider/signer
|
|
472
|
+
* @param {string} params.message - Message to sign
|
|
473
|
+
* @param {string} [params.walletAddress] - Explicit signer address (recommended)
|
|
474
|
+
* @param {string} [params.chain] - Chain context (`namespace:reference`)
|
|
475
|
+
* @returns {Promise<string>}
|
|
476
|
+
*/
|
|
477
|
+
export async function signMessage({ provider, message, walletAddress, chain } = {}) {
|
|
478
|
+
const msg = typeof message === 'string' ? message : String(message || '');
|
|
479
|
+
if (!msg) {
|
|
480
|
+
throw new SDKError('signMessage: message is required', 'INVALID_ARGUMENT');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const resolvedProvider = provider || (
|
|
484
|
+
typeof window !== 'undefined' && window?.ethereum ? window.ethereum : null
|
|
485
|
+
);
|
|
486
|
+
if (!resolvedProvider) {
|
|
487
|
+
throw new SDKError('signMessage: provider is required', 'SIGNER_UNAVAILABLE');
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const chainStr = typeof chain === 'string' && chain.trim().length > 0 ? chain.trim() : 'eip155';
|
|
491
|
+
const namespace = chainStr.includes(':') ? chainStr.split(':')[0] || 'eip155' : 'eip155';
|
|
492
|
+
|
|
493
|
+
const resolveAddress = async () => {
|
|
494
|
+
if (typeof walletAddress === 'string' && walletAddress.trim().length > 0) return walletAddress;
|
|
495
|
+
if (namespace === 'solana') {
|
|
496
|
+
if (resolvedProvider?.publicKey && typeof resolvedProvider.publicKey.toBase58 === 'function') {
|
|
497
|
+
const pk = resolvedProvider.publicKey.toBase58();
|
|
498
|
+
if (typeof pk === 'string' && pk) return pk;
|
|
499
|
+
}
|
|
500
|
+
if (typeof resolvedProvider.getAddress === 'function') {
|
|
501
|
+
const addr = await resolvedProvider.getAddress().catch(() => null);
|
|
502
|
+
if (typeof addr === 'string' && addr) return addr;
|
|
503
|
+
}
|
|
504
|
+
if (typeof resolvedProvider.address === 'string' && resolvedProvider.address) return resolvedProvider.address;
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
if (typeof resolvedProvider.address === 'string' && resolvedProvider.address) return resolvedProvider.address;
|
|
508
|
+
if (typeof resolvedProvider.getAddress === 'function') return await resolvedProvider.getAddress();
|
|
509
|
+
if (typeof resolvedProvider.request === 'function') {
|
|
510
|
+
let accounts = await resolvedProvider.request({ method: 'eth_accounts' }).catch(() => []);
|
|
511
|
+
if (!Array.isArray(accounts) || accounts.length === 0) {
|
|
512
|
+
accounts = await resolvedProvider.request({ method: 'eth_requestAccounts' }).catch(() => []);
|
|
513
|
+
}
|
|
514
|
+
if (Array.isArray(accounts) && accounts[0]) return accounts[0];
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
if (namespace !== 'eip155') {
|
|
520
|
+
if (typeof resolvedProvider.signMessage === 'function') {
|
|
521
|
+
const encoded = typeof msg === 'string' ? new TextEncoder().encode(msg) : msg;
|
|
522
|
+
const result = await resolvedProvider.signMessage(encoded);
|
|
523
|
+
if (typeof result === 'string' && result) return result;
|
|
524
|
+
if (result instanceof Uint8Array) return encodeBase58Bytes(result);
|
|
525
|
+
if (result instanceof ArrayBuffer) return encodeBase58Bytes(new Uint8Array(result));
|
|
526
|
+
if (ArrayBuffer.isView(result)) return encodeBase58Bytes(result);
|
|
527
|
+
if (typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(result)) return encodeBase58Bytes(result);
|
|
528
|
+
}
|
|
529
|
+
throw new SDKError('Non-EVM signing requires provider.signMessage', 'SIGNER_UNAVAILABLE');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const address = await resolveAddress();
|
|
533
|
+
|
|
534
|
+
if (typeof resolvedProvider.request === 'function' && address) {
|
|
535
|
+
let firstPersonalSignError = null;
|
|
536
|
+
try {
|
|
537
|
+
const sig = await resolvedProvider.request({ method: 'personal_sign', params: [msg, address] });
|
|
538
|
+
if (typeof sig === 'string' && sig) return sig;
|
|
539
|
+
} catch (error) {
|
|
540
|
+
firstPersonalSignError = error;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let secondPersonalSignError = null;
|
|
544
|
+
try {
|
|
545
|
+
const sig = await resolvedProvider.request({ method: 'personal_sign', params: [address, msg] });
|
|
546
|
+
if (typeof sig === 'string' && sig) return sig;
|
|
547
|
+
} catch (error) {
|
|
548
|
+
secondPersonalSignError = error;
|
|
549
|
+
const signatureErrorMessage = String(
|
|
550
|
+
error?.message ||
|
|
551
|
+
error?.reason ||
|
|
552
|
+
firstPersonalSignError?.message ||
|
|
553
|
+
firstPersonalSignError?.reason ||
|
|
554
|
+
''
|
|
555
|
+
).toLowerCase();
|
|
556
|
+
const needsHex = /byte|bytes|invalid byte sequence|encoding|non-hex/i.test(signatureErrorMessage);
|
|
557
|
+
if (needsHex) {
|
|
558
|
+
try {
|
|
559
|
+
const hexMsg = toHexUtf8(msg);
|
|
560
|
+
const sig = await resolvedProvider.request({ method: 'personal_sign', params: [hexMsg, address] });
|
|
561
|
+
if (typeof sig === 'string' && sig) return sig;
|
|
562
|
+
} catch {
|
|
563
|
+
// Continue to additional fallbacks.
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
try {
|
|
569
|
+
const sig = await resolvedProvider.request({ method: 'eth_sign', params: [address, msg] });
|
|
570
|
+
if (typeof sig === 'string' && sig) return sig;
|
|
571
|
+
} catch { /* try next method */ }
|
|
572
|
+
|
|
573
|
+
if (secondPersonalSignError || firstPersonalSignError) {
|
|
574
|
+
const lastError = secondPersonalSignError || firstPersonalSignError;
|
|
575
|
+
const isUserRejection = [4001, 'ACTION_REJECTED'].includes(lastError?.code);
|
|
576
|
+
if (isUserRejection) {
|
|
577
|
+
throw lastError;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (typeof resolvedProvider.signMessage === 'function') {
|
|
583
|
+
const result = await resolvedProvider.signMessage(msg);
|
|
584
|
+
if (typeof result === 'string' && result) return result;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
throw new SDKError('Unable to sign message with provided wallet/provider', 'SIGNER_UNAVAILABLE');
|
|
588
|
+
}
|
|
589
|
+
|
|
187
590
|
/**
|
|
188
591
|
* Determine if a verification status is terminal (completed or failed)
|
|
189
592
|
* @param {string} status - The verification status
|
|
@@ -572,14 +975,14 @@ export function validateVerifierPayload(verifierId, data) {
|
|
|
572
975
|
if (!(key in data)) result.missing.push(key);
|
|
573
976
|
});
|
|
574
977
|
if (!('ownerAddress' in data)) {
|
|
575
|
-
result.warnings.push('ownerAddress omitted (
|
|
978
|
+
result.warnings.push('ownerAddress omitted (defaults to the signed walletAddress)');
|
|
576
979
|
}
|
|
577
980
|
} else if (id === 'token-holding') {
|
|
578
981
|
['contractAddress', 'minBalance', 'chainId'].forEach((key) => {
|
|
579
982
|
if (!(key in data)) result.missing.push(key);
|
|
580
983
|
});
|
|
581
984
|
if (!('ownerAddress' in data)) {
|
|
582
|
-
result.warnings.push('ownerAddress omitted (
|
|
985
|
+
result.warnings.push('ownerAddress omitted (defaults to the signed walletAddress)');
|
|
583
986
|
}
|
|
584
987
|
} else if (id === 'ownership-basic') {
|
|
585
988
|
// ownership-basic requires an owner, and needs at least one binding:
|
package/widgets/README.md
CHANGED
|
@@ -1,53 +1,64 @@
|
|
|
1
|
-
# NEUS Widgets (VerifyGate + ProofBadge)
|
|
2
|
-
|
|
3
|
-
React components for NEUS verification and access gating.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @neus/sdk react react-dom
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## VerifyGate
|
|
12
|
-
|
|
13
|
-
Gate UI behind verification requirements.
|
|
14
|
-
|
|
15
|
-
```jsx
|
|
16
|
-
import { VerifyGate } from '@neus/sdk/widgets';
|
|
17
|
-
|
|
18
|
-
export function Page() {
|
|
19
|
-
return (
|
|
20
|
-
<VerifyGate
|
|
21
|
-
requiredVerifiers={['nft-ownership']}
|
|
22
|
-
verifierData={{
|
|
23
|
-
'nft-ownership': { contractAddress: '0x...', tokenId: '1', chainId:
|
|
24
|
-
}}
|
|
25
|
-
>
|
|
26
|
-
<div>Unlocked</div>
|
|
27
|
-
</VerifyGate>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Key props
|
|
33
|
-
|
|
34
|
-
- `requiredVerifiers`: `string[]` (default: `['ownership-basic']`)
|
|
35
|
-
- `verifierData`: object keyed by verifier id
|
|
36
|
-
- `strategy`: `'reuse-or-create' | 'reuse' | 'fresh'` (default: `'reuse-or-create'`)
|
|
37
|
-
- `proofOptions`: `{ privacyLevel, publicDisplay, storeOriginalContent, enableIpfs? }` (defaults: private)
|
|
38
|
-
- `mode`: `'create' | 'access'` (default: `'create'`)
|
|
39
|
-
- `
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
# NEUS Widgets (VerifyGate + ProofBadge)
|
|
2
|
+
|
|
3
|
+
React components for NEUS verification and access gating.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @neus/sdk react react-dom
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## VerifyGate
|
|
12
|
+
|
|
13
|
+
Gate UI behind verification requirements.
|
|
14
|
+
|
|
15
|
+
```jsx
|
|
16
|
+
import { VerifyGate } from '@neus/sdk/widgets';
|
|
17
|
+
|
|
18
|
+
export function Page() {
|
|
19
|
+
return (
|
|
20
|
+
<VerifyGate
|
|
21
|
+
requiredVerifiers={['nft-ownership']}
|
|
22
|
+
verifierData={{
|
|
23
|
+
'nft-ownership': { contractAddress: '0x...', tokenId: '1', chainId: 8453 }
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<div>Unlocked</div>
|
|
27
|
+
</VerifyGate>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Key props
|
|
33
|
+
|
|
34
|
+
- `requiredVerifiers`: `string[]` (default: `['ownership-basic']`)
|
|
35
|
+
- `verifierData`: object keyed by verifier id
|
|
36
|
+
- `strategy`: `'reuse-or-create' | 'reuse' | 'fresh'` (default: `'reuse-or-create'`)
|
|
37
|
+
- `proofOptions`: `{ privacyLevel, publicDisplay, storeOriginalContent, enableIpfs? }` (defaults: private)
|
|
38
|
+
- `mode`: `'create' | 'access'` (default: `'create'`)
|
|
39
|
+
- `proofId`: string (required for `mode="access"`)
|
|
40
|
+
- `maxProofAgeMs`: `number` (optional) - max proof age override in milliseconds for reuse checks
|
|
41
|
+
- `onError`: `(error: Error) => void` (optional) - called when verification/gating errors occur
|
|
42
|
+
- `apiUrl`: protocol API base URL (optional)
|
|
43
|
+
- `hostedCheckoutUrl`: hosted verify page URL (optional, recommended when `apiUrl` is custom)
|
|
44
|
+
|
|
45
|
+
Notes:
|
|
46
|
+
- Reuse without prompting can only see **public + discoverable** proofs.
|
|
47
|
+
- Reusing private proofs requires an **owner signature** (wallet grants read access).
|
|
48
|
+
- Interactive verifiers (`ownership-social`, `ownership-org-oauth`, `proof-of-human`) use NEUS hosted checkout automatically when required.
|
|
49
|
+
- If you set a custom `apiUrl`, also set `hostedCheckoutUrl` to your hosted verify UI (for example `https://neus.network/verify`).
|
|
50
|
+
`apiUrl` is used for API calls; `hostedCheckoutUrl` is used for popup checkout routing.
|
|
51
|
+
|
|
52
|
+
## ProofBadge
|
|
53
|
+
|
|
54
|
+
Display verification status by Proof ID (`proofId`).
|
|
55
|
+
|
|
56
|
+
```jsx
|
|
57
|
+
import { ProofBadge } from '@neus/sdk/widgets';
|
|
58
|
+
|
|
59
|
+
<ProofBadge proofId="0x..." showChains />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Notes:
|
|
63
|
+
- Widgets default to a bundled NEUS logo asset (inlined at build time), so no external logo fetch is required.
|
|
64
|
+
- You can override with `logoUrl` in `ProofBadge`, `SimpleProofBadge`, `NeusPillLink`, and `VerifiedIcon`.
|
package/widgets/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NEUS Widgets
|
|
3
|
-
*
|
|
4
|
-
* Core Widgets:
|
|
5
|
-
* - VerifyGate: Universal verification gate component
|
|
6
|
-
* - ProofBadge: Display verification status
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export * from './verify-gate/index.js';
|
|
1
|
+
/**
|
|
2
|
+
* NEUS Widgets
|
|
3
|
+
*
|
|
4
|
+
* Core Widgets:
|
|
5
|
+
* - VerifyGate: Universal verification gate component
|
|
6
|
+
* - ProofBadge: Display verification status
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './verify-gate/index.js';
|