@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/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 (most deployments default to the signed walletAddress)');
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 (most deployments default to the signed walletAddress)');
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: 1 }
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
- - `qHash`: string (required for `mode="access"`)
40
-
41
- Notes:
42
- - Reuse without prompting can only see **public + discoverable** proofs.
43
- - Reusing private proofs requires an **owner signature** (wallet grants read access).
44
-
45
- ## ProofBadge
46
-
47
- Display verification status by Proof ID (`qHash`).
48
-
49
- ```jsx
50
- import { ProofBadge } from '@neus/sdk/widgets';
51
-
52
- <ProofBadge qHash="0x..." showChains />
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';