@neus/sdk 1.0.3 → 1.0.4
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 +90 -150
- package/SECURITY.md +33 -29
- package/cjs/client.cjs +235 -264
- package/cjs/index.cjs +330 -310
- package/cjs/utils.cjs +138 -97
- package/cli/neus.mjs +58 -0
- package/client.js +464 -457
- package/errors.js +5 -5
- package/gates.js +18 -18
- package/index.js +79 -75
- package/package.json +16 -10
- package/types.d.ts +124 -59
- package/utils.js +1291 -1180
- package/widgets/README.md +45 -64
- package/widgets/index.js +1 -1
- package/widgets/verify-gate/dist/ProofBadge.js +10 -18
- package/widgets/verify-gate/dist/VerifyGate.js +108 -97
- package/widgets/verify-gate/index.js +5 -5
package/client.js
CHANGED
|
@@ -1,313 +1,306 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NEUS SDK Client
|
|
3
|
-
* Create and verify cryptographic proofs across applications
|
|
4
|
-
* @license Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { ApiError, ValidationError, NetworkError, ConfigurationError } from './errors.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
|
|
2
|
+
import {
|
|
3
|
+
PORTABLE_PROOF_SIGNER_HEADER,
|
|
4
|
+
constructVerificationMessage,
|
|
5
|
+
validateWalletAddress,
|
|
6
|
+
validateUniversalAddress,
|
|
7
|
+
signMessage,
|
|
8
|
+
NEUS_CONSTANTS
|
|
9
|
+
} from './utils.js';
|
|
10
|
+
|
|
11
|
+
const FALLBACK_PUBLIC_VERIFIER_CATALOG = {
|
|
12
|
+
'ownership-basic': { supportsDirectApi: true },
|
|
13
|
+
'ownership-pseudonym': { supportsDirectApi: true },
|
|
14
|
+
'ownership-dns-txt': { supportsDirectApi: true },
|
|
15
|
+
'ownership-social': { supportsDirectApi: false },
|
|
16
|
+
'ownership-org-oauth': { supportsDirectApi: false },
|
|
17
|
+
'contract-ownership': { supportsDirectApi: true },
|
|
18
|
+
'nft-ownership': { supportsDirectApi: true },
|
|
19
|
+
'token-holding': { supportsDirectApi: true },
|
|
20
|
+
'wallet-link': { supportsDirectApi: true },
|
|
21
|
+
'wallet-risk': { supportsDirectApi: true },
|
|
22
|
+
'proof-of-human': { supportsDirectApi: false },
|
|
23
|
+
'agent-identity': { supportsDirectApi: true },
|
|
24
|
+
'agent-delegation': { supportsDirectApi: true },
|
|
25
|
+
'ai-content-moderation': { supportsDirectApi: true }
|
|
26
|
+
};
|
|
32
27
|
|
|
33
28
|
const EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
34
29
|
|
|
35
|
-
// Validation for supported verifiers
|
|
36
30
|
const validateVerifierData = (verifierId, data) => {
|
|
37
31
|
if (!data || typeof data !== 'object') {
|
|
38
32
|
return { valid: false, error: 'Data object is required' };
|
|
39
33
|
}
|
|
40
|
-
|
|
41
|
-
// Format validation for supported verifiers
|
|
34
|
+
|
|
42
35
|
switch (verifierId) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
case 'ownership-basic':
|
|
37
|
+
if (!data.owner || !validateUniversalAddress(data.owner, typeof data.chain === 'string' ? data.chain : undefined)) {
|
|
38
|
+
return { valid: false, error: 'owner (universal wallet address) is required' };
|
|
39
|
+
}
|
|
40
|
+
if (data.content !== undefined && data.content !== null) {
|
|
41
|
+
if (typeof data.content !== 'string') {
|
|
42
|
+
return { valid: false, error: 'content must be a string when provided' };
|
|
49
43
|
}
|
|
50
|
-
if (data.content
|
|
51
|
-
|
|
52
|
-
return { valid: false, error: 'content must be a string when provided' };
|
|
53
|
-
}
|
|
54
|
-
if (data.content.length > 50000) {
|
|
55
|
-
return { valid: false, error: 'content exceeds 50KB inline limit' };
|
|
56
|
-
}
|
|
44
|
+
if (data.content.length > 50000) {
|
|
45
|
+
return { valid: false, error: 'content exceeds 50KB inline limit' };
|
|
57
46
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
47
|
+
}
|
|
48
|
+
if (data.contentHash !== undefined && data.contentHash !== null) {
|
|
49
|
+
if (typeof data.contentHash !== 'string' || !/^0x[a-fA-F0-9]{64}$/.test(data.contentHash)) {
|
|
50
|
+
return { valid: false, error: 'contentHash must be a 32-byte hex string (0x + 64 hex chars) when provided' };
|
|
62
51
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
52
|
+
}
|
|
53
|
+
if (data.contentType !== undefined && data.contentType !== null) {
|
|
54
|
+
if (typeof data.contentType !== 'string' || data.contentType.length > 100) {
|
|
55
|
+
return { valid: false, error: 'contentType must be a string (max 100 chars) when provided' };
|
|
56
|
+
}
|
|
57
|
+
const base = String(data.contentType).split(';')[0].trim().toLowerCase();
|
|
58
|
+
// Minimal MIME sanity check (server validates more precisely).
|
|
59
|
+
if (!base || base.includes(' ') || !base.includes('/')) {
|
|
60
|
+
return { valid: false, error: 'contentType must be a valid MIME type when provided' };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (data.provenance !== undefined && data.provenance !== null) {
|
|
64
|
+
if (!data.provenance || typeof data.provenance !== 'object' || Array.isArray(data.provenance)) {
|
|
65
|
+
return { valid: false, error: 'provenance must be an object when provided' };
|
|
66
|
+
}
|
|
67
|
+
const dk = data.provenance.declaredKind;
|
|
68
|
+
if (dk !== undefined && dk !== null) {
|
|
69
|
+
const allowed = ['human', 'ai', 'mixed', 'unknown'];
|
|
70
|
+
if (typeof dk !== 'string' || !allowed.includes(dk)) {
|
|
71
|
+
return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(', ')}` };
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const ai = data.provenance.aiContext;
|
|
75
|
+
if (ai !== undefined && ai !== null) {
|
|
76
|
+
if (typeof ai !== 'object' || Array.isArray(ai)) {
|
|
77
|
+
return { valid: false, error: 'provenance.aiContext must be an object when provided' };
|
|
76
78
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(', ')}` };
|
|
79
|
+
if (ai.generatorType !== undefined && ai.generatorType !== null) {
|
|
80
|
+
const allowed = ['local', 'saas', 'agent'];
|
|
81
|
+
if (typeof ai.generatorType !== 'string' || !allowed.includes(ai.generatorType)) {
|
|
82
|
+
return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(', ')}` };
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return { valid: false, error: 'provenance.aiContext must be an object when provided' };
|
|
88
|
-
}
|
|
89
|
-
if (ai.generatorType !== undefined && ai.generatorType !== null) {
|
|
90
|
-
const allowed = ['local', 'saas', 'agent'];
|
|
91
|
-
if (typeof ai.generatorType !== 'string' || !allowed.includes(ai.generatorType)) {
|
|
92
|
-
return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(', ')}` };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (ai.provider !== undefined && ai.provider !== null) {
|
|
96
|
-
if (typeof ai.provider !== 'string' || ai.provider.length > 64) {
|
|
97
|
-
return { valid: false, error: 'provenance.aiContext.provider must be a string (max 64 chars) when provided' };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (ai.model !== undefined && ai.model !== null) {
|
|
101
|
-
if (typeof ai.model !== 'string' || ai.model.length > 128) {
|
|
102
|
-
return { valid: false, error: 'provenance.aiContext.model must be a string (max 128 chars) when provided' };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (ai.runId !== undefined && ai.runId !== null) {
|
|
106
|
-
if (typeof ai.runId !== 'string' || ai.runId.length > 128) {
|
|
107
|
-
return { valid: false, error: 'provenance.aiContext.runId must be a string (max 128 chars) when provided' };
|
|
108
|
-
}
|
|
85
|
+
if (ai.provider !== undefined && ai.provider !== null) {
|
|
86
|
+
if (typeof ai.provider !== 'string' || ai.provider.length > 64) {
|
|
87
|
+
return { valid: false, error: 'provenance.aiContext.provider must be a string (max 64 chars) when provided' };
|
|
109
88
|
}
|
|
110
89
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
90
|
+
if (ai.model !== undefined && ai.model !== null) {
|
|
91
|
+
if (typeof ai.model !== 'string' || ai.model.length > 128) {
|
|
92
|
+
return { valid: false, error: 'provenance.aiContext.model must be a string (max 128 chars) when provided' };
|
|
93
|
+
}
|
|
115
94
|
}
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
if (ai.runId !== undefined && ai.runId !== null) {
|
|
96
|
+
if (typeof ai.runId !== 'string' || ai.runId.length > 128) {
|
|
97
|
+
return { valid: false, error: 'provenance.aiContext.runId must be a string (max 128 chars) when provided' };
|
|
98
|
+
}
|
|
120
99
|
}
|
|
121
100
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
if (!data.reference.id || typeof data.reference.id !== 'string') {
|
|
127
|
-
return { valid: false, error: 'reference.id is required when neither content nor contentHash is provided' };
|
|
128
|
-
}
|
|
129
|
-
if (!data.reference.type || typeof data.reference.type !== 'string') {
|
|
130
|
-
return { valid: false, error: 'reference.type is required when neither content nor contentHash is provided' };
|
|
131
|
-
}
|
|
101
|
+
}
|
|
102
|
+
if (data.reference !== undefined) {
|
|
103
|
+
if (!data.reference || typeof data.reference !== 'object') {
|
|
104
|
+
return { valid: false, error: 'reference must be an object when provided' };
|
|
132
105
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
!data.contractAddress ||
|
|
138
|
-
data.tokenId === null ||
|
|
139
|
-
data.tokenId === undefined ||
|
|
140
|
-
typeof data.chainId !== 'number'
|
|
141
|
-
) {
|
|
142
|
-
return { valid: false, error: 'contractAddress, tokenId, and chainId are required' };
|
|
106
|
+
if (!data.reference.type || typeof data.reference.type !== 'string') {
|
|
107
|
+
// Only required when reference object is present (or when doing reference-only proofs).
|
|
108
|
+
// Server requires reference.type when reference is used for traceability.
|
|
109
|
+
return { valid: false, error: 'reference.type is required when reference is provided' };
|
|
143
110
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
111
|
+
}
|
|
112
|
+
if (!data.content && !data.contentHash) {
|
|
113
|
+
if (!data.reference || typeof data.reference !== 'object') {
|
|
114
|
+
return { valid: false, error: 'reference is required when neither content nor contentHash is provided' };
|
|
149
115
|
}
|
|
150
|
-
if (data.
|
|
151
|
-
return { valid: false, error: '
|
|
116
|
+
if (!data.reference.id || typeof data.reference.id !== 'string') {
|
|
117
|
+
return { valid: false, error: 'reference.id is required when neither content nor contentHash is provided' };
|
|
152
118
|
}
|
|
153
|
-
if (data.
|
|
154
|
-
return { valid: false, error: '
|
|
119
|
+
if (!data.reference.type || typeof data.reference.type !== 'string') {
|
|
120
|
+
return { valid: false, error: 'reference.type is required when neither content nor contentHash is provided' };
|
|
155
121
|
}
|
|
156
|
-
|
|
157
|
-
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
case 'nft-ownership':
|
|
125
|
+
// ownerAddress is optional; server injects from request walletAddress when omitted.
|
|
126
|
+
if (
|
|
127
|
+
!data.contractAddress ||
|
|
128
|
+
data.tokenId === null ||
|
|
129
|
+
data.tokenId === undefined ||
|
|
130
|
+
typeof data.chainId !== 'number'
|
|
131
|
+
) {
|
|
132
|
+
return { valid: false, error: 'contractAddress, tokenId, and chainId are required' };
|
|
133
|
+
}
|
|
134
|
+
if (data.tokenType !== undefined && data.tokenType !== null) {
|
|
135
|
+
const tt = String(data.tokenType).toLowerCase();
|
|
136
|
+
if (tt !== 'erc721' && tt !== 'erc1155') {
|
|
137
|
+
return { valid: false, error: 'tokenType must be one of: erc721, erc1155' };
|
|
158
138
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
139
|
+
}
|
|
140
|
+
if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
|
|
141
|
+
return { valid: false, error: 'blockNumber must be an integer when provided' };
|
|
142
|
+
}
|
|
143
|
+
if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
|
|
144
|
+
return { valid: false, error: 'Invalid ownerAddress' };
|
|
145
|
+
}
|
|
146
|
+
if (!validateWalletAddress(data.contractAddress)) {
|
|
147
|
+
return { valid: false, error: 'Invalid contractAddress' };
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case 'token-holding':
|
|
151
|
+
// ownerAddress is optional; server injects from request walletAddress when omitted.
|
|
152
|
+
if (
|
|
153
|
+
!data.contractAddress ||
|
|
164
154
|
data.minBalance === null ||
|
|
165
155
|
data.minBalance === undefined ||
|
|
166
156
|
typeof data.chainId !== 'number'
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
157
|
+
) {
|
|
158
|
+
return { valid: false, error: 'contractAddress, minBalance, and chainId are required' };
|
|
159
|
+
}
|
|
160
|
+
if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
|
|
161
|
+
return { valid: false, error: 'blockNumber must be an integer when provided' };
|
|
162
|
+
}
|
|
163
|
+
if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
|
|
164
|
+
return { valid: false, error: 'Invalid ownerAddress' };
|
|
165
|
+
}
|
|
166
|
+
if (!validateWalletAddress(data.contractAddress)) {
|
|
167
|
+
return { valid: false, error: 'Invalid contractAddress' };
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
case 'ownership-dns-txt':
|
|
171
|
+
if (!data.domain || typeof data.domain !== 'string') {
|
|
172
|
+
return { valid: false, error: 'domain is required' };
|
|
173
|
+
}
|
|
174
|
+
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
175
|
+
return { valid: false, error: 'Invalid walletAddress' };
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
case 'wallet-link':
|
|
179
|
+
if (!data.primaryWalletAddress || !validateUniversalAddress(data.primaryWalletAddress, data.chain)) {
|
|
180
|
+
return { valid: false, error: 'primaryWalletAddress is required' };
|
|
181
|
+
}
|
|
182
|
+
if (!data.secondaryWalletAddress || !validateUniversalAddress(data.secondaryWalletAddress, data.chain)) {
|
|
183
|
+
return { valid: false, error: 'secondaryWalletAddress is required' };
|
|
184
|
+
}
|
|
185
|
+
if (!data.signature || typeof data.signature !== 'string') {
|
|
186
|
+
return { valid: false, error: 'signature is required (signed by secondary wallet)' };
|
|
187
|
+
}
|
|
188
|
+
if (typeof data.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
|
|
189
|
+
return { valid: false, error: 'chain is required (namespace:reference)' };
|
|
190
|
+
}
|
|
191
|
+
if (typeof data.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
|
|
192
|
+
return { valid: false, error: 'signatureMethod is required' };
|
|
193
|
+
}
|
|
194
|
+
if (typeof data.signedTimestamp !== 'number') {
|
|
195
|
+
return { valid: false, error: 'signedTimestamp is required' };
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
case 'contract-ownership':
|
|
199
|
+
if (!data.contractAddress || !validateWalletAddress(data.contractAddress)) {
|
|
200
|
+
return { valid: false, error: 'contractAddress is required' };
|
|
201
|
+
}
|
|
202
|
+
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
203
|
+
return { valid: false, error: 'Invalid walletAddress' };
|
|
204
|
+
}
|
|
205
|
+
if (typeof data.chainId !== 'number') {
|
|
206
|
+
return { valid: false, error: 'chainId is required' };
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
case 'agent-identity':
|
|
210
|
+
if (!data.agentId || typeof data.agentId !== 'string' || data.agentId.length < 1 || data.agentId.length > 128) {
|
|
211
|
+
return { valid: false, error: 'agentId is required (1-128 chars)' };
|
|
212
|
+
}
|
|
213
|
+
if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
|
|
214
|
+
return { valid: false, error: 'agentWallet is required' };
|
|
215
|
+
}
|
|
216
|
+
if (data.agentType && !['ai', 'bot', 'service', 'automation', 'agent'].includes(data.agentType)) {
|
|
217
|
+
return { valid: false, error: 'agentType must be one of: ai, bot, service, automation, agent' };
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
case 'agent-delegation':
|
|
221
|
+
if (!data.controllerWallet || !validateWalletAddress(data.controllerWallet)) {
|
|
222
|
+
return { valid: false, error: 'controllerWallet is required' };
|
|
223
|
+
}
|
|
224
|
+
if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
|
|
225
|
+
return { valid: false, error: 'agentWallet is required' };
|
|
226
|
+
}
|
|
227
|
+
if (data.scope && (typeof data.scope !== 'string' || data.scope.length > 128)) {
|
|
228
|
+
return { valid: false, error: 'scope must be a string (max 128 chars)' };
|
|
229
|
+
}
|
|
230
|
+
if (data.expiresAt && (typeof data.expiresAt !== 'number' || data.expiresAt < Date.now())) {
|
|
231
|
+
return { valid: false, error: 'expiresAt must be a future timestamp' };
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
case 'ai-content-moderation':
|
|
235
|
+
if (!data.content || typeof data.content !== 'string') {
|
|
236
|
+
return { valid: false, error: 'content is required' };
|
|
237
|
+
}
|
|
238
|
+
if (!data.contentType || typeof data.contentType !== 'string') {
|
|
239
|
+
return { valid: false, error: 'contentType (MIME type) is required' };
|
|
240
|
+
}
|
|
241
|
+
{
|
|
242
|
+
// Only allow content types that are actually moderated (no "verified but skipped" bypass).
|
|
243
|
+
const contentType = String(data.contentType).split(';')[0].trim().toLowerCase();
|
|
244
|
+
const validTypes = [
|
|
245
|
+
'image/jpeg',
|
|
246
|
+
'image/png',
|
|
247
|
+
'image/webp',
|
|
248
|
+
'image/gif',
|
|
249
|
+
'text/plain',
|
|
250
|
+
'text/markdown',
|
|
251
|
+
'text/x-markdown',
|
|
252
|
+
'application/json',
|
|
253
|
+
'application/xml'
|
|
254
|
+
];
|
|
255
|
+
if (!validTypes.includes(contentType)) {
|
|
256
|
+
return { valid: false, error: `contentType must be one of: ${validTypes.join(', ')}` };
|
|
257
|
+
}
|
|
258
|
+
const isTextual = contentType.startsWith('text/') || contentType.includes('markdown');
|
|
259
|
+
if (isTextual) {
|
|
260
|
+
// Backend enforces 50KB UTF-8 limit for textual moderation payloads.
|
|
261
|
+
try {
|
|
262
|
+
const maxBytes = 50 * 1024;
|
|
263
|
+
const bytes = (typeof TextEncoder !== 'undefined')
|
|
264
|
+
? new TextEncoder().encode(data.content).length
|
|
265
|
+
: String(data.content).length;
|
|
266
|
+
if (bytes > maxBytes) {
|
|
267
|
+
return {
|
|
268
|
+
valid: false,
|
|
269
|
+
error: `content exceeds ${maxBytes} bytes limit for ai-content-moderation verifier (text)`
|
|
270
|
+
};
|
|
281
271
|
}
|
|
272
|
+
} catch {
|
|
273
|
+
// If encoding fails, defer to server.
|
|
282
274
|
}
|
|
283
275
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
// Note: signature is not required - envelope signature provides authentication
|
|
303
|
-
break;
|
|
304
|
-
case 'wallet-risk':
|
|
305
|
-
if (data.walletAddress && !validateUniversalAddress(data.walletAddress, data.chain)) {
|
|
306
|
-
return { valid: false, error: 'Invalid walletAddress' };
|
|
276
|
+
}
|
|
277
|
+
if (data.content.length > 13653334) {
|
|
278
|
+
return { valid: false, error: 'content exceeds 10MB limit' };
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
case 'ownership-pseudonym':
|
|
282
|
+
if (!data.pseudonymId || typeof data.pseudonymId !== 'string') {
|
|
283
|
+
return { valid: false, error: 'pseudonymId is required' };
|
|
284
|
+
}
|
|
285
|
+
// Validate handle format (3-64 chars, lowercase alphanumeric with ._-)
|
|
286
|
+
if (!/^[a-z0-9._-]{3,64}$/.test(data.pseudonymId.trim().toLowerCase())) {
|
|
287
|
+
return { valid: false, error: 'pseudonymId must be 3-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
|
|
288
|
+
}
|
|
289
|
+
// Validate namespace if provided (1-64 chars)
|
|
290
|
+
if (data.namespace && typeof data.namespace === 'string') {
|
|
291
|
+
if (!/^[a-z0-9._-]{1,64}$/.test(data.namespace.trim().toLowerCase())) {
|
|
292
|
+
return { valid: false, error: 'namespace must be 1-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
|
|
307
293
|
}
|
|
308
|
-
|
|
294
|
+
}
|
|
295
|
+
// Note: signature is not required - envelope signature provides authentication
|
|
296
|
+
break;
|
|
297
|
+
case 'wallet-risk':
|
|
298
|
+
if (data.walletAddress && !validateUniversalAddress(data.walletAddress, data.chain)) {
|
|
299
|
+
return { valid: false, error: 'Invalid walletAddress' };
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
309
302
|
}
|
|
310
|
-
|
|
303
|
+
|
|
311
304
|
return { valid: true };
|
|
312
305
|
};
|
|
313
306
|
|
|
@@ -319,7 +312,6 @@ export class NeusClient {
|
|
|
319
312
|
...config
|
|
320
313
|
};
|
|
321
314
|
|
|
322
|
-
// NEUS Network API
|
|
323
315
|
this.baseUrl = this.config.apiUrl || 'https://api.neus.network';
|
|
324
316
|
// Enforce HTTPS for neus.network domains to satisfy CSP and normalize URLs
|
|
325
317
|
try {
|
|
@@ -332,32 +324,25 @@ export class NeusClient {
|
|
|
332
324
|
} catch {
|
|
333
325
|
// If invalid URL string, leave as-is
|
|
334
326
|
}
|
|
335
|
-
// Normalize apiUrl on config
|
|
336
327
|
this.config.apiUrl = this.baseUrl;
|
|
337
|
-
|
|
328
|
+
|
|
329
|
+
// Default headers
|
|
330
|
+
|
|
338
331
|
this.defaultHeaders = {
|
|
339
332
|
'Content-Type': 'application/json',
|
|
340
333
|
'Accept': 'application/json',
|
|
341
334
|
'X-Neus-Sdk': 'js'
|
|
342
335
|
};
|
|
343
336
|
|
|
344
|
-
// Optional API key (server-side only; do not embed in browser apps)
|
|
345
337
|
if (typeof this.config.apiKey === 'string' && this.config.apiKey.trim().length > 0) {
|
|
346
338
|
this.defaultHeaders['Authorization'] = `Bearer ${this.config.apiKey.trim()}`;
|
|
347
339
|
}
|
|
348
|
-
// Public app attribution header (non-secret)
|
|
349
340
|
if (typeof this.config.appId === 'string' && this.config.appId.trim().length > 0) {
|
|
350
341
|
this.defaultHeaders['X-Neus-App'] = this.config.appId.trim();
|
|
351
342
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
// x402 retry receipt header
|
|
357
|
-
if (typeof this.config.paymentSignature === 'string' && this.config.paymentSignature.trim().length > 0) {
|
|
358
|
-
this.defaultHeaders['PAYMENT-SIGNATURE'] = this.config.paymentSignature.trim();
|
|
359
|
-
}
|
|
360
|
-
// Optional caller-supplied passthrough headers.
|
|
343
|
+
if (typeof this.config.paymentSignature === 'string' && this.config.paymentSignature.trim().length > 0) {
|
|
344
|
+
this.defaultHeaders['PAYMENT-SIGNATURE'] = this.config.paymentSignature.trim();
|
|
345
|
+
}
|
|
361
346
|
if (this.config.extraHeaders && typeof this.config.extraHeaders === 'object') {
|
|
362
347
|
for (const [k, v] of Object.entries(this.config.extraHeaders)) {
|
|
363
348
|
if (!k || v === undefined || v === null) continue;
|
|
@@ -368,7 +353,6 @@ export class NeusClient {
|
|
|
368
353
|
}
|
|
369
354
|
}
|
|
370
355
|
try {
|
|
371
|
-
// Attach origin in browser environments
|
|
372
356
|
if (typeof window !== 'undefined' && window.location && window.location.origin) {
|
|
373
357
|
this.defaultHeaders['X-Client-Origin'] = window.location.origin;
|
|
374
358
|
}
|
|
@@ -506,63 +490,47 @@ export class NeusClient {
|
|
|
506
490
|
});
|
|
507
491
|
}
|
|
508
492
|
|
|
509
|
-
// ============================================================================
|
|
510
|
-
// CORE VERIFICATION METHODS
|
|
511
|
-
// ============================================================================
|
|
512
|
-
|
|
513
493
|
/**
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
494
|
+
* Create a verification proof.
|
|
495
|
+
*
|
|
496
|
+
* Supports two paths:
|
|
497
|
+
* - **Auto:** Supply `verifier`, `content`, and/or `data` with an optional
|
|
498
|
+
* `wallet` provider. The SDK performs signing in the client.
|
|
499
|
+
* - **Manual:** Supply pre-signed `verifierIds`, `data`, `walletAddress`,
|
|
500
|
+
* `signature`, and `signedTimestamp`.
|
|
501
|
+
*
|
|
520
502
|
* @param {Object} params - Verification parameters
|
|
503
|
+
* @param {string} [params.verifier] - Verifier ID (auto path, e.g. 'ownership-basic')
|
|
504
|
+
* @param {string} [params.content] - Content to verify (auto path)
|
|
505
|
+
* @param {Object} [params.data] - Structured verification data
|
|
506
|
+
* @param {Object} [params.wallet] - Wallet provider (auto path)
|
|
507
|
+
* @param {Object} [params.options] - Additional options
|
|
521
508
|
* @param {Array<string>} [params.verifierIds] - Array of verifier IDs (manual path)
|
|
522
|
-
* @param {Object} [params.data] - Verification data object (manual path)
|
|
523
509
|
* @param {string} [params.walletAddress] - Wallet address that signed the request (manual path)
|
|
524
510
|
* @param {string} [params.signature] - EIP-191 signature (manual path)
|
|
525
511
|
* @param {number} [params.signedTimestamp] - Unix timestamp when signature was created (manual path)
|
|
526
|
-
* @param {number} [params.chainId] -
|
|
527
|
-
* @
|
|
528
|
-
*
|
|
529
|
-
* @
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
512
|
+
* @param {number} [params.chainId] - EVM signing-context hint; when omitted, resolved to the NEUS protocol primary chain for signing
|
|
513
|
+
* @returns {Promise<Object>} Verification result with proofId
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* // Auto path
|
|
517
|
+
* const proof = await client.verify({
|
|
518
|
+
* verifier: 'ownership-basic',
|
|
519
|
+
* content: 'Hello World',
|
|
520
|
+
* wallet: window.ethereum
|
|
521
|
+
* });
|
|
522
|
+
*
|
|
533
523
|
* @example
|
|
524
|
+
* // Manual path
|
|
534
525
|
* const proof = await client.verify({
|
|
535
526
|
* verifierIds: ['ownership-basic'],
|
|
536
|
-
* data: {
|
|
537
|
-
* content: "My content",
|
|
538
|
-
* owner: walletAddress, // or ownerAddress for nft-ownership/token-holding
|
|
539
|
-
* reference: { type: 'other', id: 'my-unique-identifier' }
|
|
540
|
-
* },
|
|
527
|
+
* data: { content: "My content", owner: walletAddress },
|
|
541
528
|
* walletAddress: '0x...',
|
|
542
529
|
* signature: '0x...',
|
|
543
530
|
* signedTimestamp: Date.now(),
|
|
544
531
|
* options: { targetChains: [421614, 11155111] }
|
|
545
532
|
* });
|
|
546
533
|
*/
|
|
547
|
-
/**
|
|
548
|
-
* Create a verification proof
|
|
549
|
-
*
|
|
550
|
-
* @param {Object} params - Verification parameters
|
|
551
|
-
* @param {string} [params.verifier] - Verifier ID (e.g., 'ownership-basic')
|
|
552
|
-
* @param {string} [params.content] - Content to verify
|
|
553
|
-
* @param {Object} [params.data] - Structured verification data
|
|
554
|
-
* @param {Object} [params.wallet] - Wallet provider
|
|
555
|
-
* @param {Object} [params.options] - Additional options
|
|
556
|
-
* @returns {Promise<Object>} Verification result with proofId (qHash is a deprecated alias)
|
|
557
|
-
*
|
|
558
|
-
* @example
|
|
559
|
-
* // Simple ownership proof
|
|
560
|
-
* const proof = await client.verify({
|
|
561
|
-
* verifier: 'ownership-basic',
|
|
562
|
-
* content: 'Hello World',
|
|
563
|
-
* wallet: window.ethereum
|
|
564
|
-
* });
|
|
565
|
-
*/
|
|
566
534
|
async verify(params) {
|
|
567
535
|
// Auto path: if no manual signature fields but auto fields are provided, perform inline wallet flow
|
|
568
536
|
if ((!params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
|
|
@@ -573,30 +541,31 @@ export class NeusClient {
|
|
|
573
541
|
throw new ValidationError('content is required and must be a string (or use data param with owner + reference)');
|
|
574
542
|
}
|
|
575
543
|
|
|
576
|
-
let
|
|
544
|
+
let verifierCatalog = FALLBACK_PUBLIC_VERIFIER_CATALOG;
|
|
577
545
|
try {
|
|
578
|
-
const discovered = await this.
|
|
579
|
-
if (
|
|
580
|
-
|
|
546
|
+
const discovered = await this.getVerifierCatalog();
|
|
547
|
+
if (discovered && discovered.metadata && Object.keys(discovered.metadata).length > 0) {
|
|
548
|
+
verifierCatalog = discovered.metadata;
|
|
581
549
|
}
|
|
582
550
|
} catch {
|
|
583
551
|
// Fallback keeps SDK usable if verifier catalog endpoint is temporarily unavailable.
|
|
584
552
|
}
|
|
553
|
+
const validVerifiers = Object.keys(verifierCatalog);
|
|
585
554
|
if (!validVerifiers.includes(verifier)) {
|
|
586
555
|
throw new ValidationError(`Invalid verifier '${verifier}'. Must be one of: ${validVerifiers.join(', ')}.`);
|
|
587
556
|
}
|
|
588
557
|
|
|
589
|
-
if (
|
|
558
|
+
if (verifierCatalog?.[verifier]?.supportsDirectApi === false) {
|
|
590
559
|
const hostedCheckoutUrl = options?.hostedCheckoutUrl || 'https://neus.network/verify';
|
|
591
560
|
throw new ValidationError(
|
|
592
561
|
`${verifier} requires hosted interactive checkout. Use VerifyGate or redirect to ${hostedCheckoutUrl}.`
|
|
593
562
|
);
|
|
594
563
|
}
|
|
595
|
-
|
|
564
|
+
|
|
596
565
|
// These verifiers require explicit data parameter (no auto-path)
|
|
597
566
|
const requiresDataParam = [
|
|
598
|
-
'ownership-dns-txt',
|
|
599
|
-
'wallet-link',
|
|
567
|
+
'ownership-dns-txt',
|
|
568
|
+
'wallet-link',
|
|
600
569
|
'contract-ownership',
|
|
601
570
|
'ownership-pseudonym',
|
|
602
571
|
'wallet-risk',
|
|
@@ -739,7 +708,10 @@ export class NeusClient {
|
|
|
739
708
|
...(data?.agentLabel && { agentLabel: data.agentLabel }),
|
|
740
709
|
...(data?.agentType && { agentType: data.agentType }),
|
|
741
710
|
...(data?.description && { description: data.description }),
|
|
742
|
-
...(data?.capabilities && { capabilities: data.capabilities })
|
|
711
|
+
...(data?.capabilities && { capabilities: data.capabilities }),
|
|
712
|
+
...(data?.instructions && { instructions: data.instructions }),
|
|
713
|
+
...(data?.skills && { skills: data.skills }),
|
|
714
|
+
...(data?.services && { services: data.services })
|
|
743
715
|
};
|
|
744
716
|
} else if (verifier === 'agent-delegation') {
|
|
745
717
|
if (!data?.agentWallet) {
|
|
@@ -752,7 +724,11 @@ export class NeusClient {
|
|
|
752
724
|
...(data?.scope && { scope: data.scope }),
|
|
753
725
|
...(data?.permissions && { permissions: data.permissions }),
|
|
754
726
|
...(data?.maxSpend && { maxSpend: data.maxSpend }),
|
|
755
|
-
...(data?.
|
|
727
|
+
...(data?.allowedPaymentTypes && { allowedPaymentTypes: data.allowedPaymentTypes }),
|
|
728
|
+
...(data?.receiptDisclosure && { receiptDisclosure: data.receiptDisclosure }),
|
|
729
|
+
...(data?.expiresAt && { expiresAt: data.expiresAt }),
|
|
730
|
+
...(data?.instructions && { instructions: data.instructions }),
|
|
731
|
+
...(data?.skills && { skills: data.skills })
|
|
756
732
|
};
|
|
757
733
|
} else if (verifier === 'ai-content-moderation') {
|
|
758
734
|
if (!data?.content) {
|
|
@@ -787,7 +763,6 @@ export class NeusClient {
|
|
|
787
763
|
...(data?.includeDetails !== undefined && { includeDetails: data.includeDetails })
|
|
788
764
|
};
|
|
789
765
|
} else {
|
|
790
|
-
// Default structure for unknown verifiers (should not reach here with validVerifiers check)
|
|
791
766
|
verificationData = data ? {
|
|
792
767
|
content,
|
|
793
768
|
owner: walletAddress,
|
|
@@ -810,7 +785,6 @@ export class NeusClient {
|
|
|
810
785
|
|
|
811
786
|
let signature;
|
|
812
787
|
try {
|
|
813
|
-
// UNIFIED SIGNING: Matches utils/core.ts personalSignUniversal exactly
|
|
814
788
|
const toHexUtf8 = (s) => {
|
|
815
789
|
try {
|
|
816
790
|
const enc = new TextEncoder();
|
|
@@ -824,7 +798,7 @@ export class NeusClient {
|
|
|
824
798
|
return hex;
|
|
825
799
|
}
|
|
826
800
|
};
|
|
827
|
-
|
|
801
|
+
|
|
828
802
|
// Detect Farcaster wallet - requires hex-encoded messages FIRST
|
|
829
803
|
const isFarcasterWallet = (() => {
|
|
830
804
|
if (typeof window === 'undefined') return false;
|
|
@@ -841,7 +815,7 @@ export class NeusClient {
|
|
|
841
815
|
}
|
|
842
816
|
return false;
|
|
843
817
|
})();
|
|
844
|
-
|
|
818
|
+
|
|
845
819
|
if (isFarcasterWallet) {
|
|
846
820
|
try {
|
|
847
821
|
const hexMsg = toHexUtf8(message);
|
|
@@ -850,7 +824,7 @@ export class NeusClient {
|
|
|
850
824
|
// Fall through
|
|
851
825
|
}
|
|
852
826
|
}
|
|
853
|
-
|
|
827
|
+
|
|
854
828
|
if (!signature) {
|
|
855
829
|
try {
|
|
856
830
|
signature = await provider.request({ method: 'personal_sign', params: [message, walletAddress] });
|
|
@@ -858,15 +832,17 @@ export class NeusClient {
|
|
|
858
832
|
const msg = String(e && (e.message || e.reason) || e || '').toLowerCase();
|
|
859
833
|
const errCode = (e && (e.code || (e.error && e.error.code))) || null;
|
|
860
834
|
const needsHex = /byte|bytes|invalid byte sequence|encoding|non-hex/i.test(msg);
|
|
861
|
-
|
|
835
|
+
|
|
836
|
+
const unsupportedRe =
|
|
837
|
+
/method.*not.*supported|unsupported|not implemented|method not found|unknown method|does not support/i;
|
|
862
838
|
const methodUnsupported = (
|
|
863
|
-
|
|
839
|
+
unsupportedRe.test(msg) ||
|
|
864
840
|
errCode === -32601 ||
|
|
865
841
|
errCode === 4200 ||
|
|
866
842
|
(msg.includes('personal_sign') && msg.includes('not')) ||
|
|
867
843
|
(msg.includes('request method') && msg.includes('not supported'))
|
|
868
844
|
);
|
|
869
|
-
|
|
845
|
+
|
|
870
846
|
if (methodUnsupported) {
|
|
871
847
|
this._log('personal_sign not supported; attempting eth_sign fallback');
|
|
872
848
|
try {
|
|
@@ -974,14 +950,15 @@ export class NeusClient {
|
|
|
974
950
|
}
|
|
975
951
|
}
|
|
976
952
|
|
|
977
|
-
// Build options payload
|
|
953
|
+
// Build options payload.
|
|
978
954
|
const optionsPayload = {
|
|
979
955
|
...(options && typeof options === 'object' ? options : {}),
|
|
980
956
|
targetChains: Array.isArray(options?.targetChains) ? options.targetChains : [],
|
|
981
957
|
// Privacy and storage options (defaults)
|
|
982
958
|
privacyLevel: options?.privacyLevel || 'private',
|
|
983
959
|
publicDisplay: options?.publicDisplay || false,
|
|
984
|
-
storeOriginalContent:
|
|
960
|
+
storeOriginalContent:
|
|
961
|
+
typeof options?.storeOriginalContent === 'boolean' ? options.storeOriginalContent : true
|
|
985
962
|
};
|
|
986
963
|
if (typeof options?.enableIpfs === 'boolean') optionsPayload.enableIpfs = options.enableIpfs;
|
|
987
964
|
|
|
@@ -997,8 +974,6 @@ export class NeusClient {
|
|
|
997
974
|
options: optionsPayload
|
|
998
975
|
};
|
|
999
976
|
|
|
1000
|
-
// SECURITY: Do not send proof signatures in Authorization headers.
|
|
1001
|
-
// Signatures belong in the request body only (they are not bearer tokens).
|
|
1002
977
|
const response = await this._makeRequest('POST', '/api/v1/verification', requestData);
|
|
1003
978
|
|
|
1004
979
|
if (!response.success) {
|
|
@@ -1008,50 +983,44 @@ export class NeusClient {
|
|
|
1008
983
|
return this._formatResponse(response);
|
|
1009
984
|
}
|
|
1010
985
|
|
|
1011
|
-
// ============================================================================
|
|
1012
|
-
// STATUS AND UTILITY METHODS
|
|
1013
|
-
// ============================================================================
|
|
1014
|
-
|
|
1015
986
|
/**
|
|
1016
|
-
* Get
|
|
987
|
+
* Get proof record by proof receipt id.
|
|
1017
988
|
*
|
|
1018
|
-
* @param {string} proofId - Proof ID (
|
|
1019
|
-
* @returns {Promise<Object>}
|
|
989
|
+
* @param {string} proofId - Proof receipt ID (0x + 64 hex).
|
|
990
|
+
* @returns {Promise<Object>} Proof record and verification state
|
|
1020
991
|
*
|
|
1021
992
|
* @example
|
|
1022
|
-
* const result = await client.
|
|
993
|
+
* const result = await client.getProof('0x...');
|
|
1023
994
|
* console.log('Status:', result.status);
|
|
1024
995
|
*/
|
|
1025
|
-
async
|
|
996
|
+
async getProof(proofId) {
|
|
1026
997
|
if (!proofId || typeof proofId !== 'string') {
|
|
1027
998
|
throw new ValidationError('proofId is required');
|
|
1028
999
|
}
|
|
1029
|
-
const response = await this._makeRequest('GET', `/api/v1/
|
|
1000
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/${proofId}`);
|
|
1030
1001
|
|
|
1031
1002
|
if (!response.success) {
|
|
1032
|
-
throw new ApiError(`Failed to get
|
|
1003
|
+
throw new ApiError(`Failed to get proof: ${response.error?.message || 'Unknown error'}`, response.error);
|
|
1033
1004
|
}
|
|
1034
1005
|
|
|
1035
1006
|
return this._formatResponse(response);
|
|
1036
1007
|
}
|
|
1037
1008
|
|
|
1038
1009
|
/**
|
|
1039
|
-
* Get private proof
|
|
1010
|
+
* Get private proof record with wallet signature
|
|
1040
1011
|
*
|
|
1041
|
-
* @param {string} proofId - Proof ID
|
|
1012
|
+
* @param {string} proofId - Proof receipt ID.
|
|
1042
1013
|
* @param {Object} wallet - Wallet provider (window.ethereum or ethers Wallet)
|
|
1043
|
-
* @returns {Promise<Object>} Private
|
|
1014
|
+
* @returns {Promise<Object>} Private proof record and verification state
|
|
1044
1015
|
*
|
|
1045
1016
|
* @example
|
|
1046
|
-
*
|
|
1047
|
-
* const privateData = await client.getPrivateStatus(proofId, window.ethereum);
|
|
1017
|
+
* const privateData = await client.getPrivateProof(proofId, window.ethereum);
|
|
1048
1018
|
*/
|
|
1049
|
-
async
|
|
1019
|
+
async getPrivateProof(proofId, wallet = null) {
|
|
1050
1020
|
if (!proofId || typeof proofId !== 'string') {
|
|
1051
1021
|
throw new ValidationError('proofId is required');
|
|
1052
1022
|
}
|
|
1053
1023
|
|
|
1054
|
-
// Allow pre-signed universal owner auth (e.g. Solana) to avoid wallet-provider assumptions.
|
|
1055
1024
|
const isPreSignedAuth = wallet &&
|
|
1056
1025
|
typeof wallet === 'object' &&
|
|
1057
1026
|
typeof wallet.walletAddress === 'string' &&
|
|
@@ -1066,7 +1035,7 @@ export class NeusClient {
|
|
|
1066
1035
|
...(typeof auth.chain === 'string' && auth.chain.trim() ? { 'x-chain': auth.chain.trim() } : {}),
|
|
1067
1036
|
...(typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim() ? { 'x-signature-method': auth.signatureMethod.trim() } : {})
|
|
1068
1037
|
};
|
|
1069
|
-
const response = await this._makeRequest('GET', `/api/v1/
|
|
1038
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/${proofId}`, null, headers);
|
|
1070
1039
|
if (!response.success) {
|
|
1071
1040
|
throw new ApiError(
|
|
1072
1041
|
`Failed to access private proof: ${response.error?.message || 'Unauthorized'}`,
|
|
@@ -1087,8 +1056,6 @@ export class NeusClient {
|
|
|
1087
1056
|
|
|
1088
1057
|
const signedTimestamp = Date.now();
|
|
1089
1058
|
|
|
1090
|
-
// IMPORTANT: This must match the server's Standard Signing String owner-access check.
|
|
1091
|
-
// Keep wire payload key `qHash` for backwards compatibility.
|
|
1092
1059
|
const message = constructVerificationMessage({
|
|
1093
1060
|
walletAddress,
|
|
1094
1061
|
signedTimestamp,
|
|
@@ -1112,8 +1079,7 @@ export class NeusClient {
|
|
|
1112
1079
|
throw new ValidationError(`Failed to sign message: ${error.message}`);
|
|
1113
1080
|
}
|
|
1114
1081
|
|
|
1115
|
-
|
|
1116
|
-
const response = await this._makeRequest('GET', `/api/v1/verification/status/${proofId}`, null, {
|
|
1082
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/${proofId}`, null, {
|
|
1117
1083
|
'x-wallet-address': walletAddress,
|
|
1118
1084
|
'x-signature': signature,
|
|
1119
1085
|
'x-signed-timestamp': signedTimestamp.toString(),
|
|
@@ -1150,26 +1116,45 @@ export class NeusClient {
|
|
|
1150
1116
|
* @returns {Promise<string[]>} Array of verifier IDs
|
|
1151
1117
|
*/
|
|
1152
1118
|
async getVerifiers() {
|
|
1119
|
+
const catalog = await this.getVerifierCatalog();
|
|
1120
|
+
return Array.isArray(catalog?.data) ? catalog.data : [];
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Get the public verifier catalog with per-verifier capabilities.
|
|
1125
|
+
* @returns {Promise<{data: string[], metadata: Record<string, { supportsDirectApi?: boolean }>, meta?: object}>}
|
|
1126
|
+
*/
|
|
1127
|
+
async getVerifierCatalog() {
|
|
1153
1128
|
const response = await this._makeRequest('GET', '/api/v1/verification/verifiers');
|
|
1154
1129
|
if (!response.success) {
|
|
1155
1130
|
throw new ApiError(`Failed to get verifiers: ${response.error?.message || 'Unknown error'}`, response.error);
|
|
1156
1131
|
}
|
|
1157
|
-
return
|
|
1132
|
+
return {
|
|
1133
|
+
data: Array.isArray(response.data) ? response.data : [],
|
|
1134
|
+
metadata:
|
|
1135
|
+
response.metadata && typeof response.metadata === 'object' && !Array.isArray(response.metadata)
|
|
1136
|
+
? response.metadata
|
|
1137
|
+
: {},
|
|
1138
|
+
meta:
|
|
1139
|
+
response.meta && typeof response.meta === 'object' && !Array.isArray(response.meta)
|
|
1140
|
+
? response.meta
|
|
1141
|
+
: {}
|
|
1142
|
+
};
|
|
1158
1143
|
}
|
|
1159
1144
|
|
|
1160
1145
|
/**
|
|
1161
1146
|
* POLL PROOF STATUS - Wait for verification completion
|
|
1162
|
-
*
|
|
1147
|
+
*
|
|
1163
1148
|
* Polls the verification status until it reaches a terminal state (completed or failed).
|
|
1164
1149
|
* Useful for providing real-time feedback to users during verification.
|
|
1165
|
-
*
|
|
1166
|
-
* @param {string} proofId - Proof ID to poll
|
|
1150
|
+
*
|
|
1151
|
+
* @param {string} proofId - Proof ID to poll.
|
|
1167
1152
|
* @param {Object} [options] - Polling options
|
|
1168
1153
|
* @param {number} [options.interval=5000] - Polling interval in ms
|
|
1169
1154
|
* @param {number} [options.timeout=120000] - Total timeout in ms
|
|
1170
1155
|
* @param {Function} [options.onProgress] - Progress callback function
|
|
1171
1156
|
* @returns {Promise<Object>} Final verification status
|
|
1172
|
-
*
|
|
1157
|
+
*
|
|
1173
1158
|
* @example
|
|
1174
1159
|
* const finalStatus = await client.pollProofStatus(proofId, {
|
|
1175
1160
|
* interval: 3000,
|
|
@@ -1195,34 +1180,34 @@ export class NeusClient {
|
|
|
1195
1180
|
|
|
1196
1181
|
const startTime = Date.now();
|
|
1197
1182
|
let consecutiveRateLimits = 0;
|
|
1198
|
-
|
|
1183
|
+
|
|
1199
1184
|
while (Date.now() - startTime < timeout) {
|
|
1200
1185
|
try {
|
|
1201
|
-
const status = await this.
|
|
1186
|
+
const status = await this.getProof(proofId);
|
|
1202
1187
|
consecutiveRateLimits = 0;
|
|
1203
|
-
|
|
1188
|
+
|
|
1204
1189
|
// Call progress callback if provided
|
|
1205
1190
|
if (onProgress && typeof onProgress === 'function') {
|
|
1206
1191
|
onProgress(status.data || status);
|
|
1207
1192
|
}
|
|
1208
|
-
|
|
1193
|
+
|
|
1209
1194
|
// Check for terminal states
|
|
1210
1195
|
const currentStatus = status.data?.status || status.status;
|
|
1211
1196
|
if (this._isTerminalStatus(currentStatus)) {
|
|
1212
1197
|
this._log('Verification completed', { status: currentStatus, duration: Date.now() - startTime });
|
|
1213
1198
|
return status;
|
|
1214
1199
|
}
|
|
1215
|
-
|
|
1200
|
+
|
|
1216
1201
|
// Wait before next poll
|
|
1217
1202
|
await new Promise(resolve => setTimeout(resolve, interval));
|
|
1218
|
-
|
|
1203
|
+
|
|
1219
1204
|
} catch (error) {
|
|
1220
1205
|
this._log('Status poll error', error.message);
|
|
1221
1206
|
// Continue polling unless it's a validation error
|
|
1222
1207
|
if (error instanceof ValidationError) {
|
|
1223
1208
|
throw error;
|
|
1224
1209
|
}
|
|
1225
|
-
|
|
1210
|
+
|
|
1226
1211
|
let nextDelay = interval;
|
|
1227
1212
|
if (error instanceof ApiError && Number(error.statusCode) === 429) {
|
|
1228
1213
|
consecutiveRateLimits += 1;
|
|
@@ -1233,17 +1218,17 @@ export class NeusClient {
|
|
|
1233
1218
|
const jitter = Math.floor(backoff * (0.5 + Math.random() * 0.5)); // 50-100%
|
|
1234
1219
|
nextDelay = jitter;
|
|
1235
1220
|
}
|
|
1236
|
-
|
|
1221
|
+
|
|
1237
1222
|
await new Promise(resolve => setTimeout(resolve, nextDelay));
|
|
1238
1223
|
}
|
|
1239
1224
|
}
|
|
1240
|
-
|
|
1225
|
+
|
|
1241
1226
|
throw new NetworkError(`Polling timeout after ${timeout}ms`, 'POLLING_TIMEOUT');
|
|
1242
1227
|
}
|
|
1243
1228
|
|
|
1244
1229
|
/**
|
|
1245
1230
|
* DETECT CHAIN ID - Get current wallet chain
|
|
1246
|
-
*
|
|
1231
|
+
*
|
|
1247
1232
|
* @returns {Promise<number>} Current chain ID
|
|
1248
1233
|
*/
|
|
1249
1234
|
async detectChainId() {
|
|
@@ -1277,7 +1262,6 @@ export class NeusClient {
|
|
|
1277
1262
|
const message = constructVerificationMessage({
|
|
1278
1263
|
walletAddress: address,
|
|
1279
1264
|
signedTimestamp,
|
|
1280
|
-
// Keep wire payload key `qHash` for backwards compatibility.
|
|
1281
1265
|
data: { action: 'revoke_proof', qHash: proofId },
|
|
1282
1266
|
verifierIds: ['ownership-basic'],
|
|
1283
1267
|
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
|
|
@@ -1298,9 +1282,8 @@ export class NeusClient {
|
|
|
1298
1282
|
throw new ValidationError(`Failed to sign revocation: ${error.message}`);
|
|
1299
1283
|
}
|
|
1300
1284
|
|
|
1301
|
-
const res = await fetch(`${this.config.apiUrl}/api/v1/proofs/${proofId}
|
|
1285
|
+
const res = await fetch(`${this.config.apiUrl}/api/v1/proofs/revoke-self/${proofId}`, {
|
|
1302
1286
|
method: 'POST',
|
|
1303
|
-
// SECURITY: Do not put proof signatures into Authorization headers.
|
|
1304
1287
|
headers: { 'Content-Type': 'application/json' },
|
|
1305
1288
|
body: JSON.stringify({
|
|
1306
1289
|
walletAddress: address,
|
|
@@ -1316,19 +1299,16 @@ export class NeusClient {
|
|
|
1316
1299
|
return true;
|
|
1317
1300
|
}
|
|
1318
1301
|
|
|
1319
|
-
// ============================================================================
|
|
1320
|
-
// PROOFS & GATING METHODS
|
|
1321
|
-
// ============================================================================
|
|
1322
|
-
|
|
1323
1302
|
/**
|
|
1324
1303
|
* GET PROOFS BY WALLET - Fetch proofs for a wallet address
|
|
1325
|
-
*
|
|
1304
|
+
*
|
|
1326
1305
|
* @param {string} walletAddress - Wallet identity (EVM/Solana/DID)
|
|
1327
1306
|
* @param {Object} [options] - Filter options
|
|
1328
1307
|
* @param {number} [options.limit] - Max results (default: 50; higher limits require owner access)
|
|
1329
1308
|
* @param {number} [options.offset] - Pagination offset (default: 0)
|
|
1309
|
+
* @param {string} [options.qHash] - Filter to single proof by qHash
|
|
1330
1310
|
* @returns {Promise<Object>} Proofs result
|
|
1331
|
-
*
|
|
1311
|
+
*
|
|
1332
1312
|
* @example
|
|
1333
1313
|
* const result = await client.getProofsByWallet('0x...', {
|
|
1334
1314
|
* limit: 50,
|
|
@@ -1346,11 +1326,12 @@ export class NeusClient {
|
|
|
1346
1326
|
const qs = [];
|
|
1347
1327
|
if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
|
|
1348
1328
|
if (options.offset) qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
|
|
1329
|
+
if (options.qHash) qs.push(`qHash=${encodeURIComponent(options.qHash.toLowerCase())}`);
|
|
1349
1330
|
|
|
1350
1331
|
const query = qs.length ? `?${qs.join('&')}` : '';
|
|
1351
1332
|
const response = await this._makeRequest(
|
|
1352
1333
|
'GET',
|
|
1353
|
-
`/api/v1/proofs/
|
|
1334
|
+
`/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`
|
|
1354
1335
|
);
|
|
1355
1336
|
|
|
1356
1337
|
if (!response.success) {
|
|
@@ -1377,6 +1358,7 @@ export class NeusClient {
|
|
|
1377
1358
|
* @param {Object} [options]
|
|
1378
1359
|
* @param {number} [options.limit] - Max results (server enforces caps)
|
|
1379
1360
|
* @param {number} [options.offset] - Pagination offset
|
|
1361
|
+
* @param {string} [options.qHash] - Filter to single proof by qHash
|
|
1380
1362
|
* @param {Object} [wallet] - Optional injected wallet/provider (MetaMask/ethers Wallet)
|
|
1381
1363
|
*/
|
|
1382
1364
|
async getPrivateProofsByWallet(walletAddress, options = {}, wallet = null) {
|
|
@@ -1442,9 +1424,10 @@ export class NeusClient {
|
|
|
1442
1424
|
const qs = [];
|
|
1443
1425
|
if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
|
|
1444
1426
|
if (options.offset) qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
|
|
1427
|
+
if (options.qHash) qs.push(`qHash=${encodeURIComponent(options.qHash.toLowerCase())}`);
|
|
1445
1428
|
const query = qs.length ? `?${qs.join('&')}` : '';
|
|
1446
1429
|
|
|
1447
|
-
const response = await this._makeRequest('GET', `/api/v1/proofs/
|
|
1430
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`, null, {
|
|
1448
1431
|
'x-wallet-address': signerWalletAddress,
|
|
1449
1432
|
'x-signature': signature,
|
|
1450
1433
|
'x-signed-timestamp': signedTimestamp.toString(),
|
|
@@ -1466,16 +1449,16 @@ export class NeusClient {
|
|
|
1466
1449
|
}
|
|
1467
1450
|
|
|
1468
1451
|
/**
|
|
1469
|
-
*
|
|
1452
|
+
* Gate check (HTTP API) — minimal eligibility response.
|
|
1470
1453
|
*
|
|
1471
1454
|
* Calls the gate endpoint and returns a **minimal** yes/no response.
|
|
1472
|
-
* By default this checks **public +
|
|
1455
|
+
* By default this checks **public + unlisted** proofs.
|
|
1473
1456
|
*
|
|
1474
1457
|
* When `includePrivate=true`, this can perform owner-signed private checks
|
|
1475
1458
|
* (no full proof payloads returned) by providing a wallet/provider.
|
|
1476
1459
|
*
|
|
1477
|
-
* Prefer this over `checkGate()`
|
|
1478
|
-
*
|
|
1460
|
+
* Prefer this over `checkGate()` when you need the smallest, most stable
|
|
1461
|
+
* response shape and do not need full proof payloads.
|
|
1479
1462
|
*
|
|
1480
1463
|
* @param {Object} params - Gate check query params
|
|
1481
1464
|
* @param {string} params.address - Wallet identity to check (EVM/Solana/DID)
|
|
@@ -1550,6 +1533,8 @@ export class NeusClient {
|
|
|
1550
1533
|
|
|
1551
1534
|
// Wallet filters
|
|
1552
1535
|
setIfPresent('provider', params.provider);
|
|
1536
|
+
setIfPresent('handle', params.handle);
|
|
1537
|
+
setIfPresent('namespace', params.namespace);
|
|
1553
1538
|
setIfPresent('ownerAddress', params.ownerAddress);
|
|
1554
1539
|
setIfPresent('riskLevel', params.riskLevel);
|
|
1555
1540
|
setBoolIfPresent('sanctioned', params.sanctioned);
|
|
@@ -1574,7 +1559,7 @@ export class NeusClient {
|
|
|
1574
1559
|
}
|
|
1575
1560
|
}
|
|
1576
1561
|
if (!auth) {
|
|
1577
|
-
//
|
|
1562
|
+
// Without a signer: public and unlisted proofs only.
|
|
1578
1563
|
} else {
|
|
1579
1564
|
const normalizedAuthWallet = this._normalizeIdentity(auth.walletAddress);
|
|
1580
1565
|
const normalizedAddress = this._normalizeIdentity(address);
|
|
@@ -1603,11 +1588,17 @@ export class NeusClient {
|
|
|
1603
1588
|
}
|
|
1604
1589
|
|
|
1605
1590
|
/**
|
|
1606
|
-
* CHECK GATE
|
|
1607
|
-
*
|
|
1591
|
+
* CHECK GATE — Local preview against proofs you already have in memory or from `getProofsByWallet`.
|
|
1592
|
+
*
|
|
1593
|
+
* **Not authoritative for access control.** For production allow/deny, use {@link NeusClient#gateCheck}
|
|
1594
|
+
* (`GET /api/v1/proofs/check`), which applies the same rules as the NEUS API. This method is useful for
|
|
1595
|
+
* UI previews, offline-ish flows, or when you already fetched proofs and want a quick match without
|
|
1596
|
+
* another round trip — but it can disagree with the server (e.g. `contentHash` matching uses a local
|
|
1597
|
+
* approximation when proof data only has inline `content`; the API uses the standard server-side hash).
|
|
1598
|
+
*
|
|
1608
1599
|
* Gate-first verification: checks if wallet has valid proofs satisfying requirements.
|
|
1609
1600
|
* Returns which requirements are missing/expired.
|
|
1610
|
-
*
|
|
1601
|
+
*
|
|
1611
1602
|
* @param {Object} params - Gate check parameters
|
|
1612
1603
|
* @param {string} params.walletAddress - Target wallet
|
|
1613
1604
|
* @param {Array<Object>} params.requirements - Array of gate requirements
|
|
@@ -1625,7 +1616,7 @@ export class NeusClient {
|
|
|
1625
1616
|
* Note: contentHash matching uses approximation in SDK; for exact SHA-256 matching, use backend API
|
|
1626
1617
|
* @param {Array} [params.proofs] - Pre-fetched proofs (skip API call)
|
|
1627
1618
|
* @returns {Promise<Object>} Gate result with satisfied, missing, existing
|
|
1628
|
-
*
|
|
1619
|
+
*
|
|
1629
1620
|
* @example
|
|
1630
1621
|
* // Basic gate check
|
|
1631
1622
|
* const result = await client.checkGate({
|
|
@@ -1682,20 +1673,28 @@ export class NeusClient {
|
|
|
1682
1673
|
if (match && typeof match === 'object') {
|
|
1683
1674
|
const data = verifier.data || {};
|
|
1684
1675
|
const input = data.input || {}; // NFT/token verifiers store fields in input
|
|
1685
|
-
|
|
1686
|
-
|
|
1676
|
+
// Gate format: match as array [{path, op, value}]. Convert to {path: value} for iteration.
|
|
1677
|
+
const matchObj = Array.isArray(match)
|
|
1678
|
+
? Object.fromEntries(
|
|
1679
|
+
match
|
|
1680
|
+
.filter((m) => m && m.path && String(m.value ?? '').trim() !== '')
|
|
1681
|
+
.map((m) => [String(m.path).trim(), m.value])
|
|
1682
|
+
)
|
|
1683
|
+
: match;
|
|
1684
|
+
|
|
1685
|
+
for (const [key, expected] of Object.entries(matchObj)) {
|
|
1687
1686
|
let actualValue = null;
|
|
1688
|
-
|
|
1687
|
+
|
|
1689
1688
|
// Handle nested field access
|
|
1690
1689
|
if (key.includes('.')) {
|
|
1691
1690
|
const parts = key.split('.');
|
|
1692
1691
|
let current = data;
|
|
1693
|
-
|
|
1692
|
+
|
|
1694
1693
|
if (parts[0] === 'input' && input) {
|
|
1695
1694
|
current = input;
|
|
1696
1695
|
parts.shift();
|
|
1697
1696
|
}
|
|
1698
|
-
|
|
1697
|
+
|
|
1699
1698
|
for (const part of parts) {
|
|
1700
1699
|
if (current && typeof current === 'object' && part in current) {
|
|
1701
1700
|
current = current[part];
|
|
@@ -1708,13 +1707,14 @@ export class NeusClient {
|
|
|
1708
1707
|
} else {
|
|
1709
1708
|
actualValue = input[key] || data[key];
|
|
1710
1709
|
}
|
|
1711
|
-
|
|
1710
|
+
|
|
1712
1711
|
if (key === 'content' && actualValue === undefined) {
|
|
1713
1712
|
actualValue = data.reference?.id || data.content;
|
|
1714
1713
|
}
|
|
1715
|
-
|
|
1716
|
-
// Special handling for verifier-specific fields
|
|
1714
|
+
|
|
1715
|
+
// Special handling for verifier-specific fields (claim-based, aligns with proofs/check)
|
|
1717
1716
|
if (actualValue === undefined) {
|
|
1717
|
+
const claims = data.claims || {};
|
|
1718
1718
|
if (key === 'contractAddress') {
|
|
1719
1719
|
actualValue = input.contractAddress || data.contractAddress;
|
|
1720
1720
|
} else if (key === 'tokenId') {
|
|
@@ -1733,9 +1733,28 @@ export class NeusClient {
|
|
|
1733
1733
|
actualValue = data.verificationMethod;
|
|
1734
1734
|
} else if (key === 'domain') {
|
|
1735
1735
|
actualValue = data.domain;
|
|
1736
|
+
} else if (key === 'handle') {
|
|
1737
|
+
actualValue = data.handle || data.pseudonymId;
|
|
1738
|
+
} else if (key === 'namespace') {
|
|
1739
|
+
actualValue =
|
|
1740
|
+
data.namespace !== undefined && data.namespace !== null && data.namespace !== ''
|
|
1741
|
+
? data.namespace
|
|
1742
|
+
: 'neus';
|
|
1743
|
+
} else if (key === 'claims.sanctions_passed') {
|
|
1744
|
+
actualValue = claims.sanctions_passed ?? claims.sanctionsPassed;
|
|
1745
|
+
} else if (key === 'claims.age_min') {
|
|
1746
|
+
actualValue = claims.age_min ?? claims.ageMin;
|
|
1747
|
+
} else if (key === 'neusPersonhoodId') {
|
|
1748
|
+
actualValue = data.neusPersonhoodId;
|
|
1749
|
+
} else if (key === 'riskLevel') {
|
|
1750
|
+
actualValue = data.riskLevel;
|
|
1751
|
+
} else if (key === 'sanctioned') {
|
|
1752
|
+
actualValue = data.sanctioned;
|
|
1753
|
+
} else if (key === 'poisoned') {
|
|
1754
|
+
actualValue = data.poisoned;
|
|
1736
1755
|
}
|
|
1737
1756
|
}
|
|
1738
|
-
|
|
1757
|
+
|
|
1739
1758
|
// Content hash check (approximation)
|
|
1740
1759
|
if (key === 'contentHash' && actualValue === undefined && data.content) {
|
|
1741
1760
|
try {
|
|
@@ -1747,19 +1766,27 @@ export class NeusClient {
|
|
|
1747
1766
|
hash = ((hash << 5) - hash) + char;
|
|
1748
1767
|
hash = hash & hash;
|
|
1749
1768
|
}
|
|
1750
|
-
actualValue =
|
|
1769
|
+
actualValue = `0x${ Math.abs(hash).toString(16).padStart(64, '0').substring(0, 66)}`;
|
|
1751
1770
|
} catch {
|
|
1752
1771
|
actualValue = String(data.content);
|
|
1753
1772
|
}
|
|
1754
1773
|
}
|
|
1755
|
-
|
|
1774
|
+
|
|
1756
1775
|
let normalizedActual = actualValue;
|
|
1757
1776
|
let normalizedExpected = expected;
|
|
1758
|
-
|
|
1777
|
+
|
|
1759
1778
|
if (actualValue === undefined || actualValue === null) {
|
|
1760
1779
|
return false;
|
|
1761
1780
|
}
|
|
1762
|
-
|
|
1781
|
+
|
|
1782
|
+
// Boolean claims (sanctions_passed, sanctioned, poisoned)
|
|
1783
|
+
if (typeof actualValue === 'boolean' || (key && (key.includes('sanctions') || key.includes('sanctioned') || key.includes('poisoned')))) {
|
|
1784
|
+
const bActual = Boolean(actualValue);
|
|
1785
|
+
const bExpected = expected === true || expected === 'true' || String(expected).toLowerCase() === 'true';
|
|
1786
|
+
if (bActual !== bExpected) return false;
|
|
1787
|
+
continue;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1763
1790
|
if (key === 'chainId' || (key === 'tokenId' && (typeof actualValue === 'number' || !isNaN(Number(actualValue))))) {
|
|
1764
1791
|
normalizedActual = Number(actualValue);
|
|
1765
1792
|
normalizedExpected = Number(expected);
|
|
@@ -1773,7 +1800,7 @@ export class NeusClient {
|
|
|
1773
1800
|
normalizedActual = actualValue;
|
|
1774
1801
|
normalizedExpected = expected;
|
|
1775
1802
|
}
|
|
1776
|
-
|
|
1803
|
+
|
|
1777
1804
|
if (normalizedActual !== normalizedExpected) {
|
|
1778
1805
|
return false;
|
|
1779
1806
|
}
|
|
@@ -1799,14 +1826,6 @@ export class NeusClient {
|
|
|
1799
1826
|
};
|
|
1800
1827
|
}
|
|
1801
1828
|
|
|
1802
|
-
// ============================================================================
|
|
1803
|
-
// PRIVATE UTILITY METHODS
|
|
1804
|
-
// ============================================================================
|
|
1805
|
-
|
|
1806
|
-
/**
|
|
1807
|
-
* Get connected wallet address
|
|
1808
|
-
* @private
|
|
1809
|
-
*/
|
|
1810
1829
|
async _getWalletAddress() {
|
|
1811
1830
|
if (typeof window === 'undefined' || !window.ethereum) {
|
|
1812
1831
|
throw new ConfigurationError('No Web3 wallet detected');
|
|
@@ -1820,16 +1839,12 @@ export class NeusClient {
|
|
|
1820
1839
|
return accounts[0];
|
|
1821
1840
|
}
|
|
1822
1841
|
|
|
1823
|
-
/**
|
|
1824
|
-
* Make HTTP request to API
|
|
1825
|
-
* @private
|
|
1826
|
-
*/
|
|
1827
1842
|
async _makeRequest(method, endpoint, data = null, headersOverride = null) {
|
|
1828
1843
|
const url = `${this.baseUrl}${endpoint}`;
|
|
1829
|
-
|
|
1844
|
+
|
|
1830
1845
|
const controller = new AbortController();
|
|
1831
1846
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
1832
|
-
|
|
1847
|
+
|
|
1833
1848
|
const options = {
|
|
1834
1849
|
method,
|
|
1835
1850
|
headers: { ...this.defaultHeaders, ...(headersOverride || {}) },
|
|
@@ -1845,7 +1860,7 @@ export class NeusClient {
|
|
|
1845
1860
|
try {
|
|
1846
1861
|
const response = await fetch(url, options);
|
|
1847
1862
|
clearTimeout(timeoutId);
|
|
1848
|
-
|
|
1863
|
+
|
|
1849
1864
|
let responseData;
|
|
1850
1865
|
try {
|
|
1851
1866
|
responseData = await response.json();
|
|
@@ -1861,23 +1876,19 @@ export class NeusClient {
|
|
|
1861
1876
|
|
|
1862
1877
|
} catch (error) {
|
|
1863
1878
|
clearTimeout(timeoutId);
|
|
1864
|
-
|
|
1879
|
+
|
|
1865
1880
|
if (error.name === 'AbortError') {
|
|
1866
1881
|
throw new NetworkError(`Request timeout after ${this.config.timeout}ms`);
|
|
1867
1882
|
}
|
|
1868
|
-
|
|
1883
|
+
|
|
1869
1884
|
if (error instanceof ApiError) {
|
|
1870
1885
|
throw error;
|
|
1871
1886
|
}
|
|
1872
|
-
|
|
1887
|
+
|
|
1873
1888
|
throw new NetworkError(`Network error: ${error.message}`);
|
|
1874
1889
|
}
|
|
1875
1890
|
}
|
|
1876
1891
|
|
|
1877
|
-
/**
|
|
1878
|
-
* Format API response for consistent structure
|
|
1879
|
-
* @private
|
|
1880
|
-
*/
|
|
1881
1892
|
_formatResponse(response) {
|
|
1882
1893
|
const proofId = response?.data?.proofId ||
|
|
1883
1894
|
response?.proofId ||
|
|
@@ -1893,9 +1904,9 @@ export class NeusClient {
|
|
|
1893
1904
|
response?.data?.id;
|
|
1894
1905
|
const finalProofId = proofId || qHash || null;
|
|
1895
1906
|
const finalQHash = qHash || proofId || finalProofId;
|
|
1896
|
-
|
|
1897
|
-
const status = response?.data?.status ||
|
|
1898
|
-
response?.status ||
|
|
1907
|
+
|
|
1908
|
+
const status = response?.data?.status ||
|
|
1909
|
+
response?.status ||
|
|
1899
1910
|
response?.data?.resource?.status ||
|
|
1900
1911
|
(response?.success ? 'completed' : 'unknown');
|
|
1901
1912
|
|
|
@@ -1907,14 +1918,10 @@ export class NeusClient {
|
|
|
1907
1918
|
data: response.data,
|
|
1908
1919
|
message: response.message,
|
|
1909
1920
|
timestamp: Date.now(),
|
|
1910
|
-
|
|
1921
|
+
proofUrl: finalProofId ? `${this.baseUrl}/api/v1/proofs/${finalProofId}` : null
|
|
1911
1922
|
};
|
|
1912
1923
|
}
|
|
1913
1924
|
|
|
1914
|
-
/**
|
|
1915
|
-
* Check if status is terminal (completed or failed)
|
|
1916
|
-
* @private
|
|
1917
|
-
*/
|
|
1918
1925
|
_isTerminalStatus(status) {
|
|
1919
1926
|
const terminalStates = [
|
|
1920
1927
|
'verified',
|
|
@@ -1947,5 +1954,5 @@ export class NeusClient {
|
|
|
1947
1954
|
}
|
|
1948
1955
|
}
|
|
1949
1956
|
|
|
1950
|
-
// Export
|
|
1951
|
-
export { constructVerificationMessage };
|
|
1957
|
+
// Export signing helpers for advanced use
|
|
1958
|
+
export { PORTABLE_PROOF_SIGNER_HEADER, constructVerificationMessage };
|