@neus/sdk 1.0.4 → 1.0.6
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 +21 -4
- package/SECURITY.md +11 -6
- package/cjs/client.cjs +132 -220
- package/cjs/gates.cjs +10 -33
- package/cjs/index.cjs +143 -252
- package/cjs/utils.cjs +0 -6
- package/cli/neus.mjs +589 -41
- package/client.js +1834 -1955
- package/errors.js +0 -34
- package/gates.js +73 -175
- package/index.js +0 -9
- package/package.json +5 -4
- package/types.d.ts +14 -459
- package/utils.js +1 -265
- package/widgets/README.md +45 -45
- package/widgets/index.js +0 -8
- package/widgets/verify-gate/dist/ProofBadge.js +0 -3
- package/widgets/verify-gate/dist/VerifyGate.js +58 -78
- package/widgets/verify-gate/index.js +0 -4
package/utils.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NEUS SDK Utilities
|
|
3
|
-
* Core utility functions for proof creation and verification
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import { SDKError, ApiError, ValidationError } from './errors.js';
|
|
7
2
|
|
|
8
|
-
/** CAIP-380 six-line signer message — line 1 (fixed context label). */
|
|
9
3
|
export const PORTABLE_PROOF_SIGNER_HEADER = 'Portable Proof Verification Request';
|
|
10
4
|
|
|
11
5
|
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
@@ -59,11 +53,6 @@ function encodeBase58Bytes(input) {
|
|
|
59
53
|
return out;
|
|
60
54
|
}
|
|
61
55
|
|
|
62
|
-
/**
|
|
63
|
-
* Deterministic JSON stringification for consistent serialization
|
|
64
|
-
* @param {Object} obj - Object to stringify
|
|
65
|
-
* @returns {string} Deterministic JSON string
|
|
66
|
-
*/
|
|
67
56
|
function deterministicStringify(obj) {
|
|
68
57
|
if (obj === null || obj === undefined) {
|
|
69
58
|
return JSON.stringify(obj);
|
|
@@ -86,9 +75,6 @@ function deterministicStringify(obj) {
|
|
|
86
75
|
return `{${ pairs.join(',') }}`;
|
|
87
76
|
}
|
|
88
77
|
|
|
89
|
-
/**
|
|
90
|
-
* CAIP-380 EVM: line3 is decimal chainId. Non-EVM: CAIP-2 chain string.
|
|
91
|
-
*/
|
|
92
78
|
function chainLineForPortableProofSigner(chain, chainId) {
|
|
93
79
|
if (typeof chain === 'string' && chain.length > 0) {
|
|
94
80
|
const m = chain.match(/^eip155:(\d+)$/);
|
|
@@ -101,19 +87,7 @@ function chainLineForPortableProofSigner(chain, chainId) {
|
|
|
101
87
|
throw new SDKError('chainId is required (or provide chain for universal mode)', 'INVALID_CHAIN_CONTEXT');
|
|
102
88
|
}
|
|
103
89
|
|
|
104
|
-
/**
|
|
105
|
-
* Construct verification message for wallet signing
|
|
106
|
-
*
|
|
107
|
-
* @param {Object} params - Message parameters
|
|
108
|
-
* @param {string} params.walletAddress - Wallet address
|
|
109
|
-
* @param {number} params.signedTimestamp - Unix timestamp
|
|
110
|
-
* @param {Object} params.data - Verification data
|
|
111
|
-
* @param {Array<string>} params.verifierIds - Array of verifier IDs
|
|
112
|
-
* @param {number} params.chainId - Chain ID
|
|
113
|
-
* @returns {string} Message for signing
|
|
114
|
-
*/
|
|
115
90
|
export function constructVerificationMessage({ walletAddress, signedTimestamp, data, verifierIds, chainId, chain }) {
|
|
116
|
-
// Input validation for critical parameters
|
|
117
91
|
if (!walletAddress || typeof walletAddress !== 'string') {
|
|
118
92
|
throw new SDKError('walletAddress is required and must be a string', 'INVALID_WALLET_ADDRESS');
|
|
119
93
|
}
|
|
@@ -139,7 +113,6 @@ export function constructVerificationMessage({ walletAddress, signedTimestamp, d
|
|
|
139
113
|
|
|
140
114
|
const chainLine = chainLineForPortableProofSigner(chain, chainId);
|
|
141
115
|
|
|
142
|
-
// Address normalization: EVM (`eip155`) is lowercased; non-EVM namespaces preserve the original string.
|
|
143
116
|
const namespace = (typeof chain === 'string' && chain.includes(':')) ? chain.split(':')[0] : 'eip155';
|
|
144
117
|
const normalizedWalletAddress = namespace === 'eip155' ? walletAddress.toLowerCase() : walletAddress;
|
|
145
118
|
|
|
@@ -157,29 +130,14 @@ export function constructVerificationMessage({ walletAddress, signedTimestamp, d
|
|
|
157
130
|
return messageComponents.join('\n').normalize('NFC');
|
|
158
131
|
}
|
|
159
132
|
|
|
160
|
-
/**
|
|
161
|
-
* Validate Ethereum wallet address format
|
|
162
|
-
*
|
|
163
|
-
* @param {string} address - Address to validate
|
|
164
|
-
* @returns {boolean} True if valid Ethereum address
|
|
165
|
-
*/
|
|
166
133
|
export function validateWalletAddress(address) {
|
|
167
134
|
if (!address || typeof address !== 'string') {
|
|
168
135
|
return false;
|
|
169
136
|
}
|
|
170
137
|
|
|
171
|
-
// Basic Ethereum address validation
|
|
172
138
|
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
173
139
|
}
|
|
174
140
|
|
|
175
|
-
/**
|
|
176
|
-
* Validate universal wallet address format.
|
|
177
|
-
* Uses chain namespace when provided; otherwise applies conservative multi-chain checks.
|
|
178
|
-
*
|
|
179
|
-
* @param {string} address - Address to validate
|
|
180
|
-
* @param {string} [chain] - Optional CAIP-2 chain reference (namespace:reference)
|
|
181
|
-
* @returns {boolean} True if valid universal wallet address
|
|
182
|
-
*/
|
|
183
141
|
export function validateUniversalAddress(address, chain) {
|
|
184
142
|
if (!address || typeof address !== 'string') return false;
|
|
185
143
|
const value = address.trim();
|
|
@@ -204,17 +162,9 @@ export function validateUniversalAddress(address, chain) {
|
|
|
204
162
|
return /^[a-z0-9._-]{2,64}$/.test(value);
|
|
205
163
|
}
|
|
206
164
|
|
|
207
|
-
// Generic fallback for universal-address style identifiers.
|
|
208
165
|
return /^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$/.test(value);
|
|
209
166
|
}
|
|
210
167
|
|
|
211
|
-
/**
|
|
212
|
-
* Validate timestamp freshness
|
|
213
|
-
*
|
|
214
|
-
* @param {number} timestamp - Timestamp to validate
|
|
215
|
-
* @param {number} maxAgeMs - Maximum age in milliseconds (default: 5 minutes)
|
|
216
|
-
* @returns {boolean} True if timestamp is valid and recent
|
|
217
|
-
*/
|
|
218
168
|
export function validateTimestamp(timestamp, maxAgeMs = 5 * 60 * 1000) {
|
|
219
169
|
if (!timestamp || typeof timestamp !== 'number') {
|
|
220
170
|
return false;
|
|
@@ -223,21 +173,10 @@ export function validateTimestamp(timestamp, maxAgeMs = 5 * 60 * 1000) {
|
|
|
223
173
|
const now = Date.now();
|
|
224
174
|
const age = now - timestamp;
|
|
225
175
|
|
|
226
|
-
// Check if timestamp is in the past and within allowed age
|
|
227
176
|
return age >= 0 && age <= maxAgeMs;
|
|
228
177
|
}
|
|
229
178
|
|
|
230
|
-
/**
|
|
231
|
-
* Create formatted verification data object
|
|
232
|
-
*
|
|
233
|
-
* @param {string} content - Content to verify
|
|
234
|
-
* @param {string} owner - Owner wallet address
|
|
235
|
-
* @param {Object} reference - Reference object
|
|
236
|
-
* @returns {Object} Formatted verification data
|
|
237
|
-
*/
|
|
238
179
|
export function createVerificationData(content, owner, reference = null) {
|
|
239
|
-
// Small, deterministic reference ID for convenience (NOT a cryptographic hash).
|
|
240
|
-
// Integrators that need a stable binding should prefer contentHash or an explicit reference.id.
|
|
241
180
|
const stableRefId = (value) => {
|
|
242
181
|
const str = typeof value === 'string' ? value : JSON.stringify(value);
|
|
243
182
|
let hash = 0;
|
|
@@ -253,17 +192,12 @@ export function createVerificationData(content, owner, reference = null) {
|
|
|
253
192
|
content,
|
|
254
193
|
owner: validateWalletAddress(owner) ? owner.toLowerCase() : owner,
|
|
255
194
|
reference: reference || {
|
|
256
|
-
// Must be a valid backend enum value; 'content' is not supported.
|
|
257
195
|
type: 'other',
|
|
258
196
|
id: stableRefId(content)
|
|
259
197
|
}
|
|
260
198
|
};
|
|
261
199
|
}
|
|
262
200
|
|
|
263
|
-
/**
|
|
264
|
-
* DERIVE DID FROM ADDRESS AND CHAIN
|
|
265
|
-
* did:pkh:<namespace>:<chainId|segment>:<address_lowercase>
|
|
266
|
-
*/
|
|
267
201
|
export function deriveDid(address, chainIdOrChain) {
|
|
268
202
|
if (!address || typeof address !== 'string') {
|
|
269
203
|
throw new SDKError('deriveDid: address is required', 'INVALID_ARGUMENT');
|
|
@@ -284,20 +218,6 @@ export function deriveDid(address, chainIdOrChain) {
|
|
|
284
218
|
}
|
|
285
219
|
}
|
|
286
220
|
|
|
287
|
-
/**
|
|
288
|
-
* Resolve DID from wallet identity via API endpoint
|
|
289
|
-
*
|
|
290
|
-
* @param {Object} params - DID resolution parameters
|
|
291
|
-
* @param {string} params.walletAddress - Wallet address to resolve
|
|
292
|
-
* @param {number} [params.chainId] - EVM chain ID
|
|
293
|
-
* @param {string} [params.chain] - Universal chain context (namespace:reference)
|
|
294
|
-
* @param {Object} [options] - Request options
|
|
295
|
-
* @param {string} [options.endpoint='/api/v1/profile/did/resolve'] - DID resolve endpoint
|
|
296
|
-
* @param {string} [options.apiUrl] - Absolute API base URL for non-relative endpoints
|
|
297
|
-
* @param {RequestCredentials} [options.credentials] - Fetch credentials mode
|
|
298
|
-
* @param {Record<string, string>} [options.headers] - Extra request headers
|
|
299
|
-
* @returns {Promise<{did: string, data: any, raw: any}>}
|
|
300
|
-
*/
|
|
301
221
|
export async function resolveDID(params, options = {}) {
|
|
302
222
|
const endpointPath = options.endpoint || '/api/v1/profile/did/resolve';
|
|
303
223
|
const apiUrl = typeof options.apiUrl === 'string' ? options.apiUrl.trim() : '';
|
|
@@ -371,17 +291,6 @@ export async function resolveDID(params, options = {}) {
|
|
|
371
291
|
}
|
|
372
292
|
}
|
|
373
293
|
|
|
374
|
-
/**
|
|
375
|
-
* Standardize verification request via backend signer-string endpoint
|
|
376
|
-
*
|
|
377
|
-
* @param {Object} params - Verification request payload
|
|
378
|
-
* @param {Object} [options] - Request options
|
|
379
|
-
* @param {string} [options.endpoint='/api/v1/verification/standardize'] - Standardize endpoint
|
|
380
|
-
* @param {string} [options.apiUrl] - Absolute API base URL for non-relative endpoints
|
|
381
|
-
* @param {RequestCredentials} [options.credentials] - Fetch credentials mode
|
|
382
|
-
* @param {Record<string, string>} [options.headers] - Extra request headers
|
|
383
|
-
* @returns {Promise<any>}
|
|
384
|
-
*/
|
|
385
294
|
export async function standardizeVerificationRequest(params, options = {}) {
|
|
386
295
|
const endpointPath = options.endpoint || '/api/v1/verification/standardize';
|
|
387
296
|
const apiUrl = typeof options.apiUrl === 'string' ? options.apiUrl.trim() : '';
|
|
@@ -443,13 +352,6 @@ export async function standardizeVerificationRequest(params, options = {}) {
|
|
|
443
352
|
}
|
|
444
353
|
}
|
|
445
354
|
|
|
446
|
-
/**
|
|
447
|
-
* Resolve default ZK Passport configuration values.
|
|
448
|
-
* Kept as an SDK utility to preserve existing app integrations.
|
|
449
|
-
*
|
|
450
|
-
* @param {Object} [overrides] - Caller-provided config overrides
|
|
451
|
-
* @returns {Object}
|
|
452
|
-
*/
|
|
453
355
|
export function resolveZkPassportConfig(overrides = {}) {
|
|
454
356
|
const defaults = {
|
|
455
357
|
provider: 'zkpassport',
|
|
@@ -465,29 +367,12 @@ export function resolveZkPassportConfig(overrides = {}) {
|
|
|
465
367
|
};
|
|
466
368
|
}
|
|
467
369
|
|
|
468
|
-
/**
|
|
469
|
-
* Convert a UTF-8 string to `0x`-prefixed hex.
|
|
470
|
-
*
|
|
471
|
-
* @param {string} value
|
|
472
|
-
* @returns {string}
|
|
473
|
-
*/
|
|
474
370
|
export function toHexUtf8(value) {
|
|
475
371
|
const input = typeof value === 'string' ? value : String(value || '');
|
|
476
372
|
const bytes = new TextEncoder().encode(input);
|
|
477
373
|
return `0x${Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')}`;
|
|
478
374
|
}
|
|
479
375
|
|
|
480
|
-
/**
|
|
481
|
-
* Sign an arbitrary message with the provided wallet/provider.
|
|
482
|
-
* Supports EIP-1193 wallets and signer-like objects.
|
|
483
|
-
*
|
|
484
|
-
* @param {Object} params
|
|
485
|
-
* @param {Object} [params.provider] - Wallet provider/signer
|
|
486
|
-
* @param {string} params.message - Message to sign
|
|
487
|
-
* @param {string} [params.walletAddress] - Explicit signer address (recommended)
|
|
488
|
-
* @param {string} [params.chain] - Chain context (`namespace:reference`)
|
|
489
|
-
* @returns {Promise<string>}
|
|
490
|
-
*/
|
|
491
376
|
export async function signMessage({ provider, message, walletAddress, chain } = {}) {
|
|
492
377
|
const msg = typeof message === 'string' ? message : String(message || '');
|
|
493
378
|
if (!msg) {
|
|
@@ -574,7 +459,7 @@ export async function signMessage({ provider, message, walletAddress, chain } =
|
|
|
574
459
|
const sig = await resolvedProvider.request({ method: 'personal_sign', params: [hexMsg, address] });
|
|
575
460
|
if (typeof sig === 'string' && sig) return sig;
|
|
576
461
|
} catch {
|
|
577
|
-
|
|
462
|
+
void 0;
|
|
578
463
|
}
|
|
579
464
|
}
|
|
580
465
|
}
|
|
@@ -601,15 +486,9 @@ export async function signMessage({ provider, message, walletAddress, chain } =
|
|
|
601
486
|
throw new SDKError('Unable to sign message with provided wallet/provider', 'SIGNER_UNAVAILABLE');
|
|
602
487
|
}
|
|
603
488
|
|
|
604
|
-
/**
|
|
605
|
-
* Determine if a verification status is terminal (completed or failed)
|
|
606
|
-
* @param {string} status - The verification status
|
|
607
|
-
* @returns {boolean} Whether the status is terminal
|
|
608
|
-
*/
|
|
609
489
|
export function isTerminalStatus(status) {
|
|
610
490
|
if (!status || typeof status !== 'string') return false;
|
|
611
491
|
|
|
612
|
-
// Success states
|
|
613
492
|
const successStates = [
|
|
614
493
|
'verified',
|
|
615
494
|
'verified_no_verifiers',
|
|
@@ -618,7 +497,6 @@ export function isTerminalStatus(status) {
|
|
|
618
497
|
'verified_propagation_failed'
|
|
619
498
|
];
|
|
620
499
|
|
|
621
|
-
// Failure states
|
|
622
500
|
const failureStates = [
|
|
623
501
|
'rejected',
|
|
624
502
|
'rejected_verifier_failure',
|
|
@@ -633,11 +511,6 @@ export function isTerminalStatus(status) {
|
|
|
633
511
|
return successStates.includes(status) || failureStates.includes(status);
|
|
634
512
|
}
|
|
635
513
|
|
|
636
|
-
/**
|
|
637
|
-
* Determine if a verification status indicates success
|
|
638
|
-
* @param {string} status - The verification status
|
|
639
|
-
* @returns {boolean} Whether the status indicates success
|
|
640
|
-
*/
|
|
641
514
|
export function isSuccessStatus(status) {
|
|
642
515
|
if (!status || typeof status !== 'string') return false;
|
|
643
516
|
|
|
@@ -652,11 +525,6 @@ export function isSuccessStatus(status) {
|
|
|
652
525
|
return successStates.includes(status);
|
|
653
526
|
}
|
|
654
527
|
|
|
655
|
-
/**
|
|
656
|
-
* Determine if a verification status indicates failure
|
|
657
|
-
* @param {string} status - The verification status
|
|
658
|
-
* @returns {boolean} Whether the status indicates failure
|
|
659
|
-
*/
|
|
660
528
|
export function isFailureStatus(status) {
|
|
661
529
|
if (!status || typeof status !== 'string') return false;
|
|
662
530
|
|
|
@@ -674,11 +542,6 @@ export function isFailureStatus(status) {
|
|
|
674
542
|
return failureStates.includes(status);
|
|
675
543
|
}
|
|
676
544
|
|
|
677
|
-
/**
|
|
678
|
-
* Format verification status for display
|
|
679
|
-
* @param {string} status - Raw status from API
|
|
680
|
-
* @returns {Object} Formatted status information
|
|
681
|
-
*/
|
|
682
545
|
export function formatVerificationStatus(status) {
|
|
683
546
|
const statusMap = {
|
|
684
547
|
'processing_verifiers': {
|
|
@@ -781,18 +644,6 @@ export function formatVerificationStatus(status) {
|
|
|
781
644
|
};
|
|
782
645
|
}
|
|
783
646
|
|
|
784
|
-
/**
|
|
785
|
-
* Compute keccak256 content hash (0x-prefixed) for arbitrary input
|
|
786
|
-
* Uses ethers (peer dependency) via dynamic import to avoid hard bundling
|
|
787
|
-
*
|
|
788
|
-
* Note: The NEUS backend uses SHAKE256 (quantum-resistant) for content hashing.
|
|
789
|
-
* For content hashes that match the backend, use the /verification/standardize
|
|
790
|
-
* endpoint to get the server-computed hash, or provide the content directly and let
|
|
791
|
-
* the backend compute the hash.
|
|
792
|
-
*
|
|
793
|
-
* @param {string|Uint8Array} input - Raw string (UTF-8) or bytes
|
|
794
|
-
* @returns {Promise<string>} 0x-prefixed keccak256 hash
|
|
795
|
-
*/
|
|
796
647
|
export async function computeContentHash(input) {
|
|
797
648
|
try {
|
|
798
649
|
const ethers = await import('ethers');
|
|
@@ -803,18 +654,10 @@ export async function computeContentHash(input) {
|
|
|
803
654
|
}
|
|
804
655
|
}
|
|
805
656
|
|
|
806
|
-
/**
|
|
807
|
-
* Create a delay/sleep function
|
|
808
|
-
* @param {number} ms - Milliseconds to wait
|
|
809
|
-
* @returns {Promise} Promise that resolves after the delay
|
|
810
|
-
*/
|
|
811
657
|
export function delay(ms) {
|
|
812
658
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
813
659
|
}
|
|
814
660
|
|
|
815
|
-
/**
|
|
816
|
-
* Status Polling Utility for tracking verification progress
|
|
817
|
-
*/
|
|
818
661
|
export class StatusPoller {
|
|
819
662
|
constructor(client, qHash, options = {}) {
|
|
820
663
|
this.client = client;
|
|
@@ -838,13 +681,11 @@ export class StatusPoller {
|
|
|
838
681
|
|
|
839
682
|
const response = await this.client.getProof(this.qHash);
|
|
840
683
|
|
|
841
|
-
// Check if verification is complete using the terminal status utility
|
|
842
684
|
if (isTerminalStatus(response.status)) {
|
|
843
685
|
resolve(response);
|
|
844
686
|
return;
|
|
845
687
|
}
|
|
846
688
|
|
|
847
|
-
// Check if we've exceeded max attempts
|
|
848
689
|
if (this.attempt >= this.options.maxAttempts) {
|
|
849
690
|
reject(new SDKError(
|
|
850
691
|
'Verification polling timeout',
|
|
@@ -853,7 +694,6 @@ export class StatusPoller {
|
|
|
853
694
|
return;
|
|
854
695
|
}
|
|
855
696
|
|
|
856
|
-
// Schedule next poll with optional exponential backoff
|
|
857
697
|
if (this.options.exponentialBackoff) {
|
|
858
698
|
this.currentInterval = Math.min(
|
|
859
699
|
this.currentInterval * 1.5,
|
|
@@ -889,20 +729,14 @@ export class StatusPoller {
|
|
|
889
729
|
}
|
|
890
730
|
};
|
|
891
731
|
|
|
892
|
-
// Start polling immediately
|
|
893
732
|
pollAttempt();
|
|
894
733
|
});
|
|
895
734
|
}
|
|
896
735
|
}
|
|
897
736
|
|
|
898
|
-
/**
|
|
899
|
-
* NEUS Network Constants
|
|
900
|
-
*/
|
|
901
737
|
export const NEUS_CONSTANTS = {
|
|
902
|
-
/** Default EVM chain id for NEUS protocol signing context (`HUB_CHAIN_ID` name kept for compatibility). */
|
|
903
738
|
HUB_CHAIN_ID: 84532,
|
|
904
739
|
|
|
905
|
-
// Supported target chains for cross-chain propagation
|
|
906
740
|
TESTNET_CHAINS: [
|
|
907
741
|
11155111, // Ethereum Sepolia
|
|
908
742
|
11155420, // Optimism Sepolia
|
|
@@ -910,15 +744,12 @@ export const NEUS_CONSTANTS = {
|
|
|
910
744
|
80002 // Polygon Amoy
|
|
911
745
|
],
|
|
912
746
|
|
|
913
|
-
// API endpoints
|
|
914
747
|
API_BASE_URL: 'https://api.neus.network',
|
|
915
748
|
API_VERSION: 'v1',
|
|
916
749
|
|
|
917
|
-
// Timeouts and limits
|
|
918
750
|
SIGNATURE_MAX_AGE_MS: 5 * 60 * 1000, // 5 minutes
|
|
919
751
|
REQUEST_TIMEOUT_MS: 30 * 1000, // 30 seconds
|
|
920
752
|
|
|
921
|
-
// Default verifier set for quick starts
|
|
922
753
|
DEFAULT_VERIFIERS: [
|
|
923
754
|
'ownership-basic',
|
|
924
755
|
'nft-ownership',
|
|
@@ -926,42 +757,18 @@ export const NEUS_CONSTANTS = {
|
|
|
926
757
|
]
|
|
927
758
|
};
|
|
928
759
|
|
|
929
|
-
/**
|
|
930
|
-
* Additional validation and utility helpers
|
|
931
|
-
*/
|
|
932
|
-
|
|
933
|
-
/**
|
|
934
|
-
* Validate qHash format (0x + 64 hex chars)
|
|
935
|
-
* @param {string} qHash - The qHash to validate
|
|
936
|
-
* @returns {boolean} True if valid qHash format
|
|
937
|
-
*/
|
|
938
760
|
export function validateQHash(qHash) {
|
|
939
761
|
return typeof qHash === 'string' && /^0x[a-fA-F0-9]{64}$/.test(qHash);
|
|
940
762
|
}
|
|
941
763
|
|
|
942
|
-
/**
|
|
943
|
-
* Format timestamp to human readable string
|
|
944
|
-
* @param {number} timestamp - Unix timestamp
|
|
945
|
-
* @returns {string} Formatted date string
|
|
946
|
-
*/
|
|
947
764
|
export function formatTimestamp(timestamp) {
|
|
948
765
|
return new Date(timestamp).toLocaleString();
|
|
949
766
|
}
|
|
950
767
|
|
|
951
|
-
/**
|
|
952
|
-
* Check if a chain ID is supported for cross-chain propagation
|
|
953
|
-
* @param {number} chainId - Chain ID to check
|
|
954
|
-
* @returns {boolean} True if supported
|
|
955
|
-
*/
|
|
956
768
|
export function isSupportedChain(chainId) {
|
|
957
769
|
return NEUS_CONSTANTS.TESTNET_CHAINS.includes(chainId) || chainId === NEUS_CONSTANTS.HUB_CHAIN_ID;
|
|
958
770
|
}
|
|
959
771
|
|
|
960
|
-
/**
|
|
961
|
-
* Normalize wallet address to lowercase (EIP-55 agnostic)
|
|
962
|
-
* @param {string} address - Wallet address to normalize
|
|
963
|
-
* @returns {string} Lowercase address
|
|
964
|
-
*/
|
|
965
772
|
export function normalizeAddress(address) {
|
|
966
773
|
if (!validateWalletAddress(address)) {
|
|
967
774
|
throw new SDKError('Invalid wallet address format', 'INVALID_ADDRESS');
|
|
@@ -969,13 +776,6 @@ export function normalizeAddress(address) {
|
|
|
969
776
|
return address.toLowerCase();
|
|
970
777
|
}
|
|
971
778
|
|
|
972
|
-
/**
|
|
973
|
-
* Validate a verifier payload for basic structural integrity.
|
|
974
|
-
* Lightweight validation checks; verifier authors should document complete schemas.
|
|
975
|
-
* @param {string} verifierId - Verifier identifier (e.g., 'ownership-basic' or custom)
|
|
976
|
-
* @param {any} data - Verifier-specific payload
|
|
977
|
-
* @returns {{ valid: boolean, error?: string, missing?: string[], warnings?: string[] }}
|
|
978
|
-
*/
|
|
979
779
|
export function validateVerifierPayload(verifierId, data) {
|
|
980
780
|
const result = { valid: true, missing: [], warnings: [] };
|
|
981
781
|
|
|
@@ -987,7 +787,6 @@ export function validateVerifierPayload(verifierId, data) {
|
|
|
987
787
|
return { valid: false, error: 'data must be a non-null object' };
|
|
988
788
|
}
|
|
989
789
|
|
|
990
|
-
// Minimal field hints for built-in verifiers
|
|
991
790
|
const id = verifierId.replace(/@\d+$/, '');
|
|
992
791
|
if (id === 'nft-ownership') {
|
|
993
792
|
['contractAddress', 'tokenId', 'chainId'].forEach((key) => {
|
|
@@ -1004,10 +803,6 @@ export function validateVerifierPayload(verifierId, data) {
|
|
|
1004
803
|
result.warnings.push('ownerAddress omitted (defaults to the signed walletAddress)');
|
|
1005
804
|
}
|
|
1006
805
|
} else if (id === 'ownership-basic') {
|
|
1007
|
-
// ownership-basic requires an owner, and needs at least one binding:
|
|
1008
|
-
// - content (inline), or
|
|
1009
|
-
// - contentHash (recommended for large content), or
|
|
1010
|
-
// - reference.id (reference-only proofs)
|
|
1011
806
|
if (!('owner' in data)) result.missing.push('owner');
|
|
1012
807
|
const hasContent = typeof data.content === 'string' && data.content.length > 0;
|
|
1013
808
|
const hasContentHash = typeof data.contentHash === 'string' && data.contentHash.length > 0;
|
|
@@ -1025,18 +820,6 @@ export function validateVerifierPayload(verifierId, data) {
|
|
|
1025
820
|
return result;
|
|
1026
821
|
}
|
|
1027
822
|
|
|
1028
|
-
/**
|
|
1029
|
-
* Build a standard verification request and signing message for manual flows.
|
|
1030
|
-
* Returns the message to sign and the request body (sans signature).
|
|
1031
|
-
* @param {Object} params
|
|
1032
|
-
* @param {string[]} params.verifierIds
|
|
1033
|
-
* @param {object} params.data
|
|
1034
|
-
* @param {string} params.walletAddress
|
|
1035
|
-
* @param {number} [params.chainId=NEUS_CONSTANTS.HUB_CHAIN_ID]
|
|
1036
|
-
* @param {object} [params.options]
|
|
1037
|
-
* @param {number} [params.signedTimestamp=Date.now()]
|
|
1038
|
-
* @returns {{ message: string, request: { verifierIds: string[], data: object, walletAddress: string, signedTimestamp: number, chainId: number, options?: object } }}
|
|
1039
|
-
*/
|
|
1040
823
|
export function buildVerificationRequest({
|
|
1041
824
|
verifierIds,
|
|
1042
825
|
data,
|
|
@@ -1078,12 +861,6 @@ export function buildVerificationRequest({
|
|
|
1078
861
|
return { message, request };
|
|
1079
862
|
}
|
|
1080
863
|
|
|
1081
|
-
/**
|
|
1082
|
-
* Create a retry utility with exponential backoff
|
|
1083
|
-
* @param {Function} fn - Function to retry
|
|
1084
|
-
* @param {Object} options - Retry options
|
|
1085
|
-
* @returns {Promise} Promise that resolves with function result
|
|
1086
|
-
*/
|
|
1087
864
|
export async function withRetry(fn, options = {}) {
|
|
1088
865
|
const {
|
|
1089
866
|
maxAttempts = 3,
|
|
@@ -1114,17 +891,6 @@ export async function withRetry(fn, options = {}) {
|
|
|
1114
891
|
throw lastError;
|
|
1115
892
|
}
|
|
1116
893
|
|
|
1117
|
-
/**
|
|
1118
|
-
* Validate signature components for debugging signature verification issues
|
|
1119
|
-
* @param {Object} params - Signature components to validate
|
|
1120
|
-
* @param {string} params.walletAddress - Wallet address
|
|
1121
|
-
* @param {string} params.signature - EIP-191 signature
|
|
1122
|
-
* @param {number} params.signedTimestamp - Unix timestamp
|
|
1123
|
-
* @param {Object} params.data - Verification data
|
|
1124
|
-
* @param {Array<string>} params.verifierIds - Array of verifier IDs
|
|
1125
|
-
* @param {number} params.chainId - Chain ID
|
|
1126
|
-
* @returns {Object} Validation result with detailed feedback
|
|
1127
|
-
*/
|
|
1128
894
|
export function validateSignatureComponents({ walletAddress, signature, signedTimestamp, data, verifierIds, chainId }) {
|
|
1129
895
|
const result = {
|
|
1130
896
|
valid: true,
|
|
@@ -1133,7 +899,6 @@ export function validateSignatureComponents({ walletAddress, signature, signedTi
|
|
|
1133
899
|
debugInfo: {}
|
|
1134
900
|
};
|
|
1135
901
|
|
|
1136
|
-
// Validate wallet address
|
|
1137
902
|
if (!validateWalletAddress(walletAddress)) {
|
|
1138
903
|
result.valid = false;
|
|
1139
904
|
result.errors.push('Invalid wallet address format - must be 0x + 40 hex characters');
|
|
@@ -1144,7 +909,6 @@ export function validateSignatureComponents({ walletAddress, signature, signedTi
|
|
|
1144
909
|
}
|
|
1145
910
|
}
|
|
1146
911
|
|
|
1147
|
-
// Validate signature format
|
|
1148
912
|
if (!signature || typeof signature !== 'string') {
|
|
1149
913
|
result.valid = false;
|
|
1150
914
|
result.errors.push('Signature is required and must be a string');
|
|
@@ -1153,7 +917,6 @@ export function validateSignatureComponents({ walletAddress, signature, signedTi
|
|
|
1153
917
|
result.errors.push('Invalid signature format - must be 0x + 130 hex characters (65 bytes)');
|
|
1154
918
|
}
|
|
1155
919
|
|
|
1156
|
-
// Validate timestamp
|
|
1157
920
|
if (!validateTimestamp(signedTimestamp)) {
|
|
1158
921
|
result.valid = false;
|
|
1159
922
|
result.errors.push('Invalid or expired timestamp - must be within 5 minutes');
|
|
@@ -1161,7 +924,6 @@ export function validateSignatureComponents({ walletAddress, signature, signedTi
|
|
|
1161
924
|
result.debugInfo.timestampAge = Date.now() - signedTimestamp;
|
|
1162
925
|
}
|
|
1163
926
|
|
|
1164
|
-
// Validate data object
|
|
1165
927
|
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
1166
928
|
result.valid = false;
|
|
1167
929
|
result.errors.push('Data must be a non-null object');
|
|
@@ -1169,19 +931,16 @@ export function validateSignatureComponents({ walletAddress, signature, signedTi
|
|
|
1169
931
|
result.debugInfo.dataString = deterministicStringify(data);
|
|
1170
932
|
}
|
|
1171
933
|
|
|
1172
|
-
// Validate verifier IDs
|
|
1173
934
|
if (!Array.isArray(verifierIds) || verifierIds.length === 0) {
|
|
1174
935
|
result.valid = false;
|
|
1175
936
|
result.errors.push('VerifierIds must be a non-empty array');
|
|
1176
937
|
}
|
|
1177
938
|
|
|
1178
|
-
// Validate chain ID
|
|
1179
939
|
if (typeof chainId !== 'number') {
|
|
1180
940
|
result.valid = false;
|
|
1181
941
|
result.errors.push('ChainId must be a number');
|
|
1182
942
|
}
|
|
1183
943
|
|
|
1184
|
-
// Generate the message that would be signed
|
|
1185
944
|
if (result.valid || result.errors.length < 3) {
|
|
1186
945
|
try {
|
|
1187
946
|
result.debugInfo.messageToSign = constructVerificationMessage({
|
|
@@ -1199,13 +958,6 @@ export function validateSignatureComponents({ walletAddress, signature, signedTi
|
|
|
1199
958
|
return result;
|
|
1200
959
|
}
|
|
1201
960
|
|
|
1202
|
-
/**
|
|
1203
|
-
* Convert a non-negative decimal display amount to agent-delegation `maxSpend`
|
|
1204
|
-
* (whole-number string in token base units, 1–78 digits).
|
|
1205
|
-
* @param {string|number} humanAmount - e.g. "100.50" or 100.5
|
|
1206
|
-
* @param {number} decimals - Number of decimal places for the token (e.g. 6 for USDC, 18 for ETH)
|
|
1207
|
-
* @returns {string}
|
|
1208
|
-
*/
|
|
1209
961
|
export function toAgentDelegationMaxSpend(humanAmount, decimals) {
|
|
1210
962
|
if (humanAmount === undefined || humanAmount === null) {
|
|
1211
963
|
throw new ValidationError('humanAmount is required', 'humanAmount', humanAmount);
|
|
@@ -1254,24 +1006,8 @@ export function toAgentDelegationMaxSpend(humanAmount, decimals) {
|
|
|
1254
1006
|
return out;
|
|
1255
1007
|
}
|
|
1256
1008
|
|
|
1257
|
-
/** Default hosted verify base URL */
|
|
1258
1009
|
export const DEFAULT_HOSTED_VERIFY_URL = 'https://neus.network/verify';
|
|
1259
1010
|
|
|
1260
|
-
/**
|
|
1261
|
-
* Build standardized hosted checkout/verify URL for your app.
|
|
1262
|
-
* Single typed entry point to avoid copy-paste errors.
|
|
1263
|
-
* @param {Object} opts
|
|
1264
|
-
* @param {string} [opts.gateId] - Gate ID for gate-backed checkout
|
|
1265
|
-
* @param {string} [opts.returnUrl] - Partner return URL (postMessage/redirect)
|
|
1266
|
-
* @param {string[]} [opts.verifiers] - Verifier IDs (comma-joined)
|
|
1267
|
-
* @param {string} [opts.preset] - Preset name (e.g. 'human')
|
|
1268
|
-
* @param {string} [opts.mode] - 'popup' or null
|
|
1269
|
-
* @param {string} [opts.intent] - 'login' for auth-code flow
|
|
1270
|
-
* @param {string} [opts.origin] - Allowed parent origin for popup completion
|
|
1271
|
-
* @param {string} [opts.oauthProvider] - Optional OAuth provider id to pre-select for social/org verifiers (hosted flow)
|
|
1272
|
-
* @param {string} [opts.baseUrl] - Hosted verify URL override
|
|
1273
|
-
* @returns {string} Full URL
|
|
1274
|
-
*/
|
|
1275
1011
|
export function getHostedCheckoutUrl(opts = {}) {
|
|
1276
1012
|
const base = typeof opts.baseUrl === 'string' && opts.baseUrl.trim()
|
|
1277
1013
|
? opts.baseUrl.replace(/\/+$/, '')
|
package/widgets/README.md
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
# NEUS Widgets
|
|
2
|
-
|
|
3
|
-
**Proof-aware React components** (VerifyGate, ProofBadge) so your UI can show verified state and gate content **using the same checks your server already trusts** - avoid re-implementing verifier rules only in the browser.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @neus/sdk react react-dom
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## VerifyGate
|
|
12
|
-
|
|
13
|
-
Create mode defaults to **
|
|
14
|
-
|
|
15
|
-
```jsx
|
|
16
|
-
import { VerifyGate } from '@neus/sdk/widgets';
|
|
17
|
-
|
|
18
|
-
export function Page() {
|
|
19
|
-
return (
|
|
20
|
-
<VerifyGate
|
|
21
|
-
appId="your-app-id"
|
|
22
|
-
requiredVerifiers={['nft-ownership']}
|
|
23
|
-
verifierData={{
|
|
24
|
-
'nft-ownership': { contractAddress: '0x...', tokenId: '1', chainId: 8453 }
|
|
25
|
-
}}
|
|
26
|
-
>
|
|
27
|
-
<div>Unlocked</div>
|
|
28
|
-
</VerifyGate>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## ProofBadge
|
|
34
|
-
|
|
35
|
-
```jsx
|
|
36
|
-
import { ProofBadge } from '@neus/sdk/widgets';
|
|
37
|
-
|
|
38
|
-
<ProofBadge proofId="0x..." showChains />
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Docs
|
|
42
|
-
|
|
43
|
-
- [Widgets Overview](https://docs.neus.network/widgets/overview)
|
|
44
|
-
- [Verify Component](https://docs.neus.network/widgets/verifygate)
|
|
45
|
-
- [Quickstart](https://docs.neus.network/quickstart)
|
|
1
|
+
# NEUS Widgets
|
|
2
|
+
|
|
3
|
+
**Proof-aware React components** (VerifyGate, ProofBadge) so your UI can show verified state and gate content **using the same checks your server already trusts** - avoid re-implementing verifier rules only in the browser.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @neus/sdk react react-dom
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## VerifyGate
|
|
12
|
+
|
|
13
|
+
Create mode defaults to **private**. Override `proofOptions` only when you intentionally need public visibility for link-based or public checks.
|
|
14
|
+
|
|
15
|
+
```jsx
|
|
16
|
+
import { VerifyGate } from '@neus/sdk/widgets';
|
|
17
|
+
|
|
18
|
+
export function Page() {
|
|
19
|
+
return (
|
|
20
|
+
<VerifyGate
|
|
21
|
+
appId="your-app-id"
|
|
22
|
+
requiredVerifiers={['nft-ownership']}
|
|
23
|
+
verifierData={{
|
|
24
|
+
'nft-ownership': { contractAddress: '0x...', tokenId: '1', chainId: 8453 }
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
<div>Unlocked</div>
|
|
28
|
+
</VerifyGate>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## ProofBadge
|
|
34
|
+
|
|
35
|
+
```jsx
|
|
36
|
+
import { ProofBadge } from '@neus/sdk/widgets';
|
|
37
|
+
|
|
38
|
+
<ProofBadge proofId="0x..." showChains />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Docs
|
|
42
|
+
|
|
43
|
+
- [Widgets Overview](https://docs.neus.network/widgets/overview)
|
|
44
|
+
- [Verify Component](https://docs.neus.network/widgets/verifygate)
|
|
45
|
+
- [Quickstart](https://docs.neus.network/quickstart)
|