@neus/sdk 1.0.0 → 1.0.1
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/LICENSE +36 -40
- package/README.md +86 -152
- package/SECURITY.md +29 -224
- package/cjs/client.cjs +1686 -0
- package/cjs/errors.cjs +202 -0
- package/cjs/gates.cjs +140 -0
- package/cjs/index.cjs +2315 -0
- package/cjs/utils.cjs +620 -0
- package/client.js +1693 -844
- package/errors.js +223 -228
- package/gates.js +175 -0
- package/index.js +21 -26
- package/package.json +68 -18
- package/types.d.ts +519 -71
- package/utils.js +752 -722
- package/widgets/README.md +53 -0
- package/widgets/index.js +9 -0
- package/widgets/verify-gate/dist/ProofBadge.js +355 -0
- package/widgets/verify-gate/dist/VerifyGate.js +601 -0
- package/widgets/verify-gate/index.js +13 -0
- package/widgets.cjs +20 -0
package/cjs/index.cjs
ADDED
|
@@ -0,0 +1,2315 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __esm = (fn, res) => function __init() {
|
|
8
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
9
|
+
};
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// errors.js
|
|
33
|
+
var SDKError, ApiError, ValidationError, NetworkError, ConfigurationError, VerificationError, AuthenticationError;
|
|
34
|
+
var init_errors = __esm({
|
|
35
|
+
"errors.js"() {
|
|
36
|
+
SDKError = class _SDKError extends Error {
|
|
37
|
+
constructor(message, code = "SDK_ERROR", details = {}) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "SDKError";
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.details = details;
|
|
42
|
+
this.timestamp = Date.now();
|
|
43
|
+
if (Error.captureStackTrace) {
|
|
44
|
+
Error.captureStackTrace(this, _SDKError);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
toJSON() {
|
|
48
|
+
return {
|
|
49
|
+
name: this.name,
|
|
50
|
+
message: this.message,
|
|
51
|
+
code: this.code,
|
|
52
|
+
details: this.details,
|
|
53
|
+
timestamp: this.timestamp
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
ApiError = class _ApiError extends SDKError {
|
|
58
|
+
constructor(message, statusCode = 500, code = "API_ERROR", response = null) {
|
|
59
|
+
super(message, code);
|
|
60
|
+
this.name = "ApiError";
|
|
61
|
+
this.statusCode = statusCode;
|
|
62
|
+
this.response = response;
|
|
63
|
+
this.isClientError = statusCode >= 400 && statusCode < 500;
|
|
64
|
+
this.isServerError = statusCode >= 500;
|
|
65
|
+
this.isRetryable = this.isServerError || statusCode === 429;
|
|
66
|
+
}
|
|
67
|
+
static fromResponse(response, responseData) {
|
|
68
|
+
const statusCode = response.status;
|
|
69
|
+
const message = responseData?.error?.message || responseData?.message || `API request failed with status ${statusCode}`;
|
|
70
|
+
const code = responseData?.error?.code || "API_ERROR";
|
|
71
|
+
return new _ApiError(message, statusCode, code, responseData);
|
|
72
|
+
}
|
|
73
|
+
toJSON() {
|
|
74
|
+
return {
|
|
75
|
+
...super.toJSON(),
|
|
76
|
+
statusCode: this.statusCode,
|
|
77
|
+
isClientError: this.isClientError,
|
|
78
|
+
isServerError: this.isServerError,
|
|
79
|
+
isRetryable: this.isRetryable
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
ValidationError = class extends SDKError {
|
|
84
|
+
constructor(message, field = null, value = null) {
|
|
85
|
+
super(message, "VALIDATION_ERROR");
|
|
86
|
+
this.name = "ValidationError";
|
|
87
|
+
this.field = field;
|
|
88
|
+
this.value = value;
|
|
89
|
+
this.isRetryable = false;
|
|
90
|
+
}
|
|
91
|
+
toJSON() {
|
|
92
|
+
return {
|
|
93
|
+
...super.toJSON(),
|
|
94
|
+
field: this.field,
|
|
95
|
+
value: this.value,
|
|
96
|
+
isRetryable: this.isRetryable
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
NetworkError = class _NetworkError extends SDKError {
|
|
101
|
+
constructor(message, code = "NETWORK_ERROR", originalError = null) {
|
|
102
|
+
super(message, code);
|
|
103
|
+
this.name = "NetworkError";
|
|
104
|
+
this.originalError = originalError;
|
|
105
|
+
this.isRetryable = true;
|
|
106
|
+
}
|
|
107
|
+
static isNetworkError(error) {
|
|
108
|
+
return error instanceof _NetworkError || error.name === "TypeError" && error.message.includes("fetch") || error.name === "AbortError" || error.code === "ENOTFOUND" || error.code === "ECONNREFUSED" || error.code === "ETIMEDOUT";
|
|
109
|
+
}
|
|
110
|
+
toJSON() {
|
|
111
|
+
return {
|
|
112
|
+
...super.toJSON(),
|
|
113
|
+
isRetryable: this.isRetryable,
|
|
114
|
+
originalError: this.originalError ? {
|
|
115
|
+
name: this.originalError.name,
|
|
116
|
+
message: this.originalError.message,
|
|
117
|
+
code: this.originalError.code
|
|
118
|
+
} : null
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
ConfigurationError = class extends SDKError {
|
|
123
|
+
constructor(message, configKey = null) {
|
|
124
|
+
super(message, "CONFIGURATION_ERROR");
|
|
125
|
+
this.name = "ConfigurationError";
|
|
126
|
+
this.configKey = configKey;
|
|
127
|
+
this.isRetryable = false;
|
|
128
|
+
}
|
|
129
|
+
toJSON() {
|
|
130
|
+
return {
|
|
131
|
+
...super.toJSON(),
|
|
132
|
+
configKey: this.configKey,
|
|
133
|
+
isRetryable: this.isRetryable
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
VerificationError = class extends SDKError {
|
|
138
|
+
constructor(message, verifierId = null, code = "VERIFICATION_ERROR") {
|
|
139
|
+
super(message, code);
|
|
140
|
+
this.name = "VerificationError";
|
|
141
|
+
this.verifierId = verifierId;
|
|
142
|
+
this.isRetryable = true;
|
|
143
|
+
}
|
|
144
|
+
toJSON() {
|
|
145
|
+
return {
|
|
146
|
+
...super.toJSON(),
|
|
147
|
+
verifierId: this.verifierId,
|
|
148
|
+
isRetryable: this.isRetryable
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
AuthenticationError = class extends SDKError {
|
|
153
|
+
constructor(message, code = "AUTHENTICATION_ERROR") {
|
|
154
|
+
super(message, code);
|
|
155
|
+
this.name = "AuthenticationError";
|
|
156
|
+
this.isRetryable = false;
|
|
157
|
+
}
|
|
158
|
+
toJSON() {
|
|
159
|
+
return {
|
|
160
|
+
...super.toJSON(),
|
|
161
|
+
isRetryable: this.isRetryable
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// utils.js
|
|
169
|
+
function deterministicStringify(obj) {
|
|
170
|
+
if (obj === null || obj === void 0) {
|
|
171
|
+
return JSON.stringify(obj);
|
|
172
|
+
}
|
|
173
|
+
if (typeof obj !== "object") {
|
|
174
|
+
return JSON.stringify(obj);
|
|
175
|
+
}
|
|
176
|
+
if (Array.isArray(obj)) {
|
|
177
|
+
return "[" + obj.map((item) => deterministicStringify(item)).join(",") + "]";
|
|
178
|
+
}
|
|
179
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
180
|
+
const pairs = sortedKeys.map(
|
|
181
|
+
(key) => JSON.stringify(key) + ":" + deterministicStringify(obj[key])
|
|
182
|
+
);
|
|
183
|
+
return "{" + pairs.join(",") + "}";
|
|
184
|
+
}
|
|
185
|
+
function constructVerificationMessage({ walletAddress, signedTimestamp, data, verifierIds, chainId, chain }) {
|
|
186
|
+
if (!walletAddress || typeof walletAddress !== "string") {
|
|
187
|
+
throw new SDKError("walletAddress is required and must be a string", "INVALID_WALLET_ADDRESS");
|
|
188
|
+
}
|
|
189
|
+
if (!signedTimestamp || typeof signedTimestamp !== "number") {
|
|
190
|
+
throw new SDKError("signedTimestamp is required and must be a number", "INVALID_TIMESTAMP");
|
|
191
|
+
}
|
|
192
|
+
if (!data || typeof data !== "object") {
|
|
193
|
+
throw new SDKError("data is required and must be an object", "INVALID_DATA");
|
|
194
|
+
}
|
|
195
|
+
if (!Array.isArray(verifierIds) || verifierIds.length === 0) {
|
|
196
|
+
throw new SDKError("verifierIds is required and must be a non-empty array", "INVALID_VERIFIER_IDS");
|
|
197
|
+
}
|
|
198
|
+
const chainContext = typeof chain === "string" && chain.length > 0 ? chain : chainId;
|
|
199
|
+
if (!chainContext) {
|
|
200
|
+
throw new SDKError("chainId is required (or provide chain for preview mode)", "INVALID_CHAIN_CONTEXT");
|
|
201
|
+
}
|
|
202
|
+
if (chainContext === chainId && typeof chainId !== "number") {
|
|
203
|
+
throw new SDKError("chainId must be a number when provided", "INVALID_CHAIN_ID");
|
|
204
|
+
}
|
|
205
|
+
if (chainContext === chain && (typeof chain !== "string" || !chain.includes(":"))) {
|
|
206
|
+
throw new SDKError('chain must be a "namespace:reference" string', "INVALID_CHAIN");
|
|
207
|
+
}
|
|
208
|
+
const namespace = typeof chain === "string" && chain.includes(":") ? chain.split(":")[0] : "eip155";
|
|
209
|
+
const normalizedWalletAddress = namespace === "eip155" ? walletAddress.toLowerCase() : walletAddress;
|
|
210
|
+
const dataString = deterministicStringify(data);
|
|
211
|
+
const messageComponents = [
|
|
212
|
+
"NEUS Verification Request",
|
|
213
|
+
`Wallet: ${normalizedWalletAddress}`,
|
|
214
|
+
`Chain: ${chainContext}`,
|
|
215
|
+
`Verifiers: ${verifierIds.join(",")}`,
|
|
216
|
+
`Data: ${dataString}`,
|
|
217
|
+
`Timestamp: ${signedTimestamp}`
|
|
218
|
+
];
|
|
219
|
+
return messageComponents.join("\n");
|
|
220
|
+
}
|
|
221
|
+
function validateWalletAddress(address) {
|
|
222
|
+
if (!address || typeof address !== "string") {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
226
|
+
}
|
|
227
|
+
function validateTimestamp(timestamp, maxAgeMs = 5 * 60 * 1e3) {
|
|
228
|
+
if (!timestamp || typeof timestamp !== "number") {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const now = Date.now();
|
|
232
|
+
const age = now - timestamp;
|
|
233
|
+
return age >= 0 && age <= maxAgeMs;
|
|
234
|
+
}
|
|
235
|
+
function createVerificationData(content, owner, reference = null) {
|
|
236
|
+
const stableRefId = (value) => {
|
|
237
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
238
|
+
let hash = 0;
|
|
239
|
+
for (let i = 0; i < str.length; i++) {
|
|
240
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
241
|
+
hash |= 0;
|
|
242
|
+
}
|
|
243
|
+
const hex = Math.abs(hash).toString(16).padStart(8, "0");
|
|
244
|
+
return `ref-id:${hex}:${str.length}`;
|
|
245
|
+
};
|
|
246
|
+
return {
|
|
247
|
+
content,
|
|
248
|
+
owner: owner.toLowerCase(),
|
|
249
|
+
reference: reference || {
|
|
250
|
+
// Must be a valid backend enum value; 'content' is not supported.
|
|
251
|
+
type: "other",
|
|
252
|
+
id: stableRefId(content)
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function deriveDid(address, chainIdOrChain) {
|
|
257
|
+
if (!address || typeof address !== "string") {
|
|
258
|
+
throw new SDKError("deriveDid: address is required", "INVALID_ARGUMENT");
|
|
259
|
+
}
|
|
260
|
+
const chainContext = chainIdOrChain || NEUS_CONSTANTS.HUB_CHAIN_ID;
|
|
261
|
+
const isCAIP = typeof chainContext === "string" && chainContext.includes(":");
|
|
262
|
+
if (isCAIP) {
|
|
263
|
+
const [namespace, segment] = chainContext.split(":");
|
|
264
|
+
const normalized = namespace === "eip155" ? address.toLowerCase() : address;
|
|
265
|
+
return `did:pkh:${namespace}:${segment}:${normalized}`;
|
|
266
|
+
} else {
|
|
267
|
+
if (typeof chainContext !== "number") {
|
|
268
|
+
throw new SDKError("deriveDid: chainId (number) or chain (namespace:reference string) is required", "INVALID_ARGUMENT");
|
|
269
|
+
}
|
|
270
|
+
return `did:pkh:eip155:${chainContext}:${address.toLowerCase()}`;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function isTerminalStatus(status) {
|
|
274
|
+
if (!status || typeof status !== "string")
|
|
275
|
+
return false;
|
|
276
|
+
const successStates = [
|
|
277
|
+
"verified",
|
|
278
|
+
"verified_no_verifiers",
|
|
279
|
+
"verified_crosschain_propagated",
|
|
280
|
+
"partially_verified",
|
|
281
|
+
"verified_propagation_failed"
|
|
282
|
+
];
|
|
283
|
+
const failureStates = [
|
|
284
|
+
"rejected",
|
|
285
|
+
"rejected_verifier_failure",
|
|
286
|
+
"rejected_zk_initiation_failure",
|
|
287
|
+
"error_processing_exception",
|
|
288
|
+
"error_initialization",
|
|
289
|
+
"error_storage_unavailable",
|
|
290
|
+
"error_storage_query",
|
|
291
|
+
"not_found"
|
|
292
|
+
];
|
|
293
|
+
return successStates.includes(status) || failureStates.includes(status);
|
|
294
|
+
}
|
|
295
|
+
function isSuccessStatus(status) {
|
|
296
|
+
if (!status || typeof status !== "string")
|
|
297
|
+
return false;
|
|
298
|
+
const successStates = [
|
|
299
|
+
"verified",
|
|
300
|
+
"verified_no_verifiers",
|
|
301
|
+
"verified_crosschain_propagated",
|
|
302
|
+
"partially_verified",
|
|
303
|
+
"verified_propagation_failed"
|
|
304
|
+
];
|
|
305
|
+
return successStates.includes(status);
|
|
306
|
+
}
|
|
307
|
+
function isFailureStatus(status) {
|
|
308
|
+
if (!status || typeof status !== "string")
|
|
309
|
+
return false;
|
|
310
|
+
const failureStates = [
|
|
311
|
+
"rejected",
|
|
312
|
+
"rejected_verifier_failure",
|
|
313
|
+
"rejected_zk_initiation_failure",
|
|
314
|
+
"error_processing_exception",
|
|
315
|
+
"error_initialization",
|
|
316
|
+
"error_storage_unavailable",
|
|
317
|
+
"error_storage_query",
|
|
318
|
+
"not_found"
|
|
319
|
+
];
|
|
320
|
+
return failureStates.includes(status);
|
|
321
|
+
}
|
|
322
|
+
function formatVerificationStatus(status) {
|
|
323
|
+
const statusMap = {
|
|
324
|
+
"processing_verifiers": {
|
|
325
|
+
label: "Processing",
|
|
326
|
+
description: "Verifiers are being executed",
|
|
327
|
+
category: "processing",
|
|
328
|
+
color: "blue"
|
|
329
|
+
},
|
|
330
|
+
"processing_zk_proofs": {
|
|
331
|
+
label: "Generating ZK Proofs",
|
|
332
|
+
description: "Zero-knowledge proofs are being generated",
|
|
333
|
+
category: "processing",
|
|
334
|
+
color: "blue"
|
|
335
|
+
},
|
|
336
|
+
"verified": {
|
|
337
|
+
label: "Verified",
|
|
338
|
+
description: "Verification completed successfully",
|
|
339
|
+
category: "success",
|
|
340
|
+
color: "green"
|
|
341
|
+
},
|
|
342
|
+
"verified_crosschain_initiated": {
|
|
343
|
+
label: "Cross-chain Initiated",
|
|
344
|
+
description: "Verification successful, cross-chain propagation started",
|
|
345
|
+
category: "processing",
|
|
346
|
+
color: "blue"
|
|
347
|
+
},
|
|
348
|
+
"verified_crosschain_propagating": {
|
|
349
|
+
label: "Cross-chain Propagating",
|
|
350
|
+
description: "Verification successful, transactions propagating to spoke chains",
|
|
351
|
+
category: "processing",
|
|
352
|
+
color: "blue"
|
|
353
|
+
},
|
|
354
|
+
"verified_crosschain_propagated": {
|
|
355
|
+
label: "Fully Propagated",
|
|
356
|
+
description: "Verification completed and propagated to all target chains",
|
|
357
|
+
category: "success",
|
|
358
|
+
color: "green"
|
|
359
|
+
},
|
|
360
|
+
"verified_no_verifiers": {
|
|
361
|
+
label: "Verified (No Verifiers)",
|
|
362
|
+
description: "Verification completed without specific verifiers",
|
|
363
|
+
category: "success",
|
|
364
|
+
color: "green"
|
|
365
|
+
},
|
|
366
|
+
"verified_propagation_failed": {
|
|
367
|
+
label: "Propagation Failed",
|
|
368
|
+
description: "Verification successful but cross-chain propagation failed",
|
|
369
|
+
category: "warning",
|
|
370
|
+
color: "orange"
|
|
371
|
+
},
|
|
372
|
+
"partially_verified": {
|
|
373
|
+
label: "Partially Verified",
|
|
374
|
+
description: "Some verifiers succeeded, others failed",
|
|
375
|
+
category: "warning",
|
|
376
|
+
color: "orange"
|
|
377
|
+
},
|
|
378
|
+
"rejected": {
|
|
379
|
+
label: "Rejected",
|
|
380
|
+
description: "Verification failed",
|
|
381
|
+
category: "error",
|
|
382
|
+
color: "red"
|
|
383
|
+
},
|
|
384
|
+
"rejected_verifier_failure": {
|
|
385
|
+
label: "Verifier Failed",
|
|
386
|
+
description: "One or more verifiers failed",
|
|
387
|
+
category: "error",
|
|
388
|
+
color: "red"
|
|
389
|
+
},
|
|
390
|
+
"rejected_zk_initiation_failure": {
|
|
391
|
+
label: "ZK Initiation Failed",
|
|
392
|
+
description: "Zero-knowledge proof generation failed to start",
|
|
393
|
+
category: "error",
|
|
394
|
+
color: "red"
|
|
395
|
+
},
|
|
396
|
+
"error_processing_exception": {
|
|
397
|
+
label: "Processing Error",
|
|
398
|
+
description: "An error occurred during verification processing",
|
|
399
|
+
category: "error",
|
|
400
|
+
color: "red"
|
|
401
|
+
},
|
|
402
|
+
"error_initialization": {
|
|
403
|
+
label: "Initialization Error",
|
|
404
|
+
description: "Failed to initialize verification",
|
|
405
|
+
category: "error",
|
|
406
|
+
color: "red"
|
|
407
|
+
},
|
|
408
|
+
"not_found": {
|
|
409
|
+
label: "Not Found",
|
|
410
|
+
description: "Verification record not found",
|
|
411
|
+
category: "error",
|
|
412
|
+
color: "red"
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
return statusMap[status] || {
|
|
416
|
+
label: status?.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()) || "Unknown",
|
|
417
|
+
description: "Unknown status",
|
|
418
|
+
category: "unknown",
|
|
419
|
+
color: "gray"
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
async function computeContentHash(input) {
|
|
423
|
+
try {
|
|
424
|
+
const ethers = await import("ethers");
|
|
425
|
+
const toBytes = typeof input === "string" ? ethers.toUtf8Bytes(input) : input;
|
|
426
|
+
return ethers.keccak256(toBytes);
|
|
427
|
+
} catch {
|
|
428
|
+
throw new SDKError('computeContentHash requires peer dependency "ethers" >= 6.0.0', "MISSING_PEER_DEP");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function delay(ms) {
|
|
432
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
433
|
+
}
|
|
434
|
+
function validateQHash(qHash) {
|
|
435
|
+
return typeof qHash === "string" && /^0x[a-fA-F0-9]{64}$/.test(qHash);
|
|
436
|
+
}
|
|
437
|
+
function formatTimestamp(timestamp) {
|
|
438
|
+
return new Date(timestamp).toLocaleString();
|
|
439
|
+
}
|
|
440
|
+
function isSupportedChain(chainId) {
|
|
441
|
+
return NEUS_CONSTANTS.TESTNET_CHAINS.includes(chainId) || chainId === NEUS_CONSTANTS.HUB_CHAIN_ID;
|
|
442
|
+
}
|
|
443
|
+
function normalizeAddress(address) {
|
|
444
|
+
if (!validateWalletAddress(address)) {
|
|
445
|
+
throw new SDKError("Invalid wallet address format", "INVALID_ADDRESS");
|
|
446
|
+
}
|
|
447
|
+
return address.toLowerCase();
|
|
448
|
+
}
|
|
449
|
+
function validateVerifierPayload(verifierId, data) {
|
|
450
|
+
const result = { valid: true, missing: [], warnings: [] };
|
|
451
|
+
if (!verifierId || typeof verifierId !== "string") {
|
|
452
|
+
return { valid: false, error: "verifierId is required and must be a string" };
|
|
453
|
+
}
|
|
454
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
455
|
+
return { valid: false, error: "data must be a non-null object" };
|
|
456
|
+
}
|
|
457
|
+
const id = verifierId.replace(/@\d+$/, "");
|
|
458
|
+
if (id === "nft-ownership") {
|
|
459
|
+
["contractAddress", "tokenId", "chainId"].forEach((key) => {
|
|
460
|
+
if (!(key in data))
|
|
461
|
+
result.missing.push(key);
|
|
462
|
+
});
|
|
463
|
+
if (!("ownerAddress" in data)) {
|
|
464
|
+
result.warnings.push("ownerAddress omitted (most deployments default to the signed walletAddress)");
|
|
465
|
+
}
|
|
466
|
+
} else if (id === "token-holding") {
|
|
467
|
+
["contractAddress", "minBalance", "chainId"].forEach((key) => {
|
|
468
|
+
if (!(key in data))
|
|
469
|
+
result.missing.push(key);
|
|
470
|
+
});
|
|
471
|
+
if (!("ownerAddress" in data)) {
|
|
472
|
+
result.warnings.push("ownerAddress omitted (most deployments default to the signed walletAddress)");
|
|
473
|
+
}
|
|
474
|
+
} else if (id === "ownership-basic") {
|
|
475
|
+
["content"].forEach((key) => {
|
|
476
|
+
if (!(key in data))
|
|
477
|
+
result.missing.push(key);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (result.missing.length > 0) {
|
|
481
|
+
result.valid = false;
|
|
482
|
+
result.error = `Missing required fields: ${result.missing.join(", ")}`;
|
|
483
|
+
}
|
|
484
|
+
return result;
|
|
485
|
+
}
|
|
486
|
+
function buildVerificationRequest({
|
|
487
|
+
verifierIds,
|
|
488
|
+
data,
|
|
489
|
+
walletAddress,
|
|
490
|
+
chainId = NEUS_CONSTANTS.HUB_CHAIN_ID,
|
|
491
|
+
options = void 0,
|
|
492
|
+
signedTimestamp = Date.now()
|
|
493
|
+
}) {
|
|
494
|
+
if (!Array.isArray(verifierIds) || verifierIds.length === 0) {
|
|
495
|
+
throw new SDKError("verifierIds must be a non-empty array", "INVALID_ARGUMENT");
|
|
496
|
+
}
|
|
497
|
+
if (!validateWalletAddress(walletAddress)) {
|
|
498
|
+
throw new SDKError("walletAddress must be a valid 0x address", "INVALID_ARGUMENT");
|
|
499
|
+
}
|
|
500
|
+
if (!data || typeof data !== "object") {
|
|
501
|
+
throw new SDKError("data must be a non-null object", "INVALID_ARGUMENT");
|
|
502
|
+
}
|
|
503
|
+
if (typeof chainId !== "number") {
|
|
504
|
+
throw new SDKError("chainId must be a number", "INVALID_ARGUMENT");
|
|
505
|
+
}
|
|
506
|
+
const message = constructVerificationMessage({
|
|
507
|
+
walletAddress,
|
|
508
|
+
signedTimestamp,
|
|
509
|
+
data,
|
|
510
|
+
verifierIds,
|
|
511
|
+
chainId
|
|
512
|
+
});
|
|
513
|
+
const request = {
|
|
514
|
+
verifierIds,
|
|
515
|
+
data,
|
|
516
|
+
walletAddress,
|
|
517
|
+
signedTimestamp,
|
|
518
|
+
chainId,
|
|
519
|
+
...options ? { options } : {}
|
|
520
|
+
};
|
|
521
|
+
return { message, request };
|
|
522
|
+
}
|
|
523
|
+
async function withRetry(fn, options = {}) {
|
|
524
|
+
const {
|
|
525
|
+
maxAttempts = 3,
|
|
526
|
+
baseDelay = 1e3,
|
|
527
|
+
maxDelay = 1e4,
|
|
528
|
+
backoffFactor = 2
|
|
529
|
+
} = options;
|
|
530
|
+
let lastError;
|
|
531
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
532
|
+
try {
|
|
533
|
+
return await fn();
|
|
534
|
+
} catch (error) {
|
|
535
|
+
lastError = error;
|
|
536
|
+
if (attempt === maxAttempts)
|
|
537
|
+
break;
|
|
538
|
+
const delayMs = Math.min(
|
|
539
|
+
baseDelay * Math.pow(backoffFactor, attempt - 1),
|
|
540
|
+
maxDelay
|
|
541
|
+
);
|
|
542
|
+
await delay(delayMs);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
throw lastError;
|
|
546
|
+
}
|
|
547
|
+
function validateSignatureComponents({ walletAddress, signature, signedTimestamp, data, verifierIds, chainId }) {
|
|
548
|
+
const result = {
|
|
549
|
+
valid: true,
|
|
550
|
+
errors: [],
|
|
551
|
+
warnings: [],
|
|
552
|
+
debugInfo: {}
|
|
553
|
+
};
|
|
554
|
+
if (!validateWalletAddress(walletAddress)) {
|
|
555
|
+
result.valid = false;
|
|
556
|
+
result.errors.push("Invalid wallet address format - must be 0x + 40 hex characters");
|
|
557
|
+
} else {
|
|
558
|
+
result.debugInfo.normalizedAddress = walletAddress.toLowerCase();
|
|
559
|
+
if (walletAddress !== walletAddress.toLowerCase()) {
|
|
560
|
+
result.warnings.push("Wallet address should be lowercase for consistency");
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (!signature || typeof signature !== "string") {
|
|
564
|
+
result.valid = false;
|
|
565
|
+
result.errors.push("Signature is required and must be a string");
|
|
566
|
+
} else if (!/^0x[a-fA-F0-9]{130}$/.test(signature)) {
|
|
567
|
+
result.valid = false;
|
|
568
|
+
result.errors.push("Invalid signature format - must be 0x + 130 hex characters (65 bytes)");
|
|
569
|
+
}
|
|
570
|
+
if (!validateTimestamp(signedTimestamp)) {
|
|
571
|
+
result.valid = false;
|
|
572
|
+
result.errors.push("Invalid or expired timestamp - must be within 5 minutes");
|
|
573
|
+
} else {
|
|
574
|
+
result.debugInfo.timestampAge = Date.now() - signedTimestamp;
|
|
575
|
+
}
|
|
576
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
577
|
+
result.valid = false;
|
|
578
|
+
result.errors.push("Data must be a non-null object");
|
|
579
|
+
} else {
|
|
580
|
+
result.debugInfo.dataString = deterministicStringify(data);
|
|
581
|
+
}
|
|
582
|
+
if (!Array.isArray(verifierIds) || verifierIds.length === 0) {
|
|
583
|
+
result.valid = false;
|
|
584
|
+
result.errors.push("VerifierIds must be a non-empty array");
|
|
585
|
+
}
|
|
586
|
+
if (typeof chainId !== "number") {
|
|
587
|
+
result.valid = false;
|
|
588
|
+
result.errors.push("ChainId must be a number");
|
|
589
|
+
}
|
|
590
|
+
if (result.valid || result.errors.length < 3) {
|
|
591
|
+
try {
|
|
592
|
+
result.debugInfo.messageToSign = constructVerificationMessage({
|
|
593
|
+
walletAddress: walletAddress?.toLowerCase() || walletAddress,
|
|
594
|
+
signedTimestamp,
|
|
595
|
+
data,
|
|
596
|
+
verifierIds,
|
|
597
|
+
chainId
|
|
598
|
+
});
|
|
599
|
+
} catch (error) {
|
|
600
|
+
result.errors.push(`Failed to construct message: ${error.message}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
var StatusPoller, NEUS_CONSTANTS;
|
|
606
|
+
var init_utils = __esm({
|
|
607
|
+
"utils.js"() {
|
|
608
|
+
init_errors();
|
|
609
|
+
StatusPoller = class {
|
|
610
|
+
constructor(client, qHash, options = {}) {
|
|
611
|
+
this.client = client;
|
|
612
|
+
this.qHash = qHash;
|
|
613
|
+
this.options = {
|
|
614
|
+
interval: 2e3,
|
|
615
|
+
// 2 seconds
|
|
616
|
+
maxAttempts: 150,
|
|
617
|
+
// 5 minutes total
|
|
618
|
+
exponentialBackoff: true,
|
|
619
|
+
maxInterval: 1e4,
|
|
620
|
+
// 10 seconds max
|
|
621
|
+
...options
|
|
622
|
+
};
|
|
623
|
+
this.attempt = 0;
|
|
624
|
+
this.currentInterval = this.options.interval;
|
|
625
|
+
}
|
|
626
|
+
async poll() {
|
|
627
|
+
return new Promise((resolve, reject) => {
|
|
628
|
+
const pollAttempt = async () => {
|
|
629
|
+
try {
|
|
630
|
+
this.attempt++;
|
|
631
|
+
const response = await this.client.getStatus(this.qHash);
|
|
632
|
+
if (isTerminalStatus(response.status)) {
|
|
633
|
+
resolve(response);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (this.attempt >= this.options.maxAttempts) {
|
|
637
|
+
reject(new SDKError(
|
|
638
|
+
"Verification polling timeout",
|
|
639
|
+
"POLLING_TIMEOUT"
|
|
640
|
+
));
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (this.options.exponentialBackoff) {
|
|
644
|
+
this.currentInterval = Math.min(
|
|
645
|
+
this.currentInterval * 1.5,
|
|
646
|
+
this.options.maxInterval
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
setTimeout(pollAttempt, this.currentInterval);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
reject(new SDKError(
|
|
652
|
+
`Polling failed: ${error.message}`,
|
|
653
|
+
"POLLING_ERROR"
|
|
654
|
+
));
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
pollAttempt();
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
NEUS_CONSTANTS = {
|
|
662
|
+
// Hub chain (where all verifications occur)
|
|
663
|
+
HUB_CHAIN_ID: 84532,
|
|
664
|
+
// Supported target chains for cross-chain propagation
|
|
665
|
+
TESTNET_CHAINS: [
|
|
666
|
+
11155111,
|
|
667
|
+
// Ethereum Sepolia
|
|
668
|
+
11155420,
|
|
669
|
+
// Optimism Sepolia
|
|
670
|
+
421614,
|
|
671
|
+
// Arbitrum Sepolia
|
|
672
|
+
80002
|
|
673
|
+
// Polygon Amoy
|
|
674
|
+
],
|
|
675
|
+
// API endpoints
|
|
676
|
+
API_BASE_URL: "https://api.neus.network",
|
|
677
|
+
API_VERSION: "v1",
|
|
678
|
+
// Timeouts and limits
|
|
679
|
+
SIGNATURE_MAX_AGE_MS: 5 * 60 * 1e3,
|
|
680
|
+
// 5 minutes
|
|
681
|
+
REQUEST_TIMEOUT_MS: 30 * 1e3,
|
|
682
|
+
// 30 seconds
|
|
683
|
+
// Default verifier set for quick starts
|
|
684
|
+
DEFAULT_VERIFIERS: [
|
|
685
|
+
"ownership-basic",
|
|
686
|
+
"nft-ownership",
|
|
687
|
+
"token-holding"
|
|
688
|
+
]
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// client.js
|
|
694
|
+
var client_exports = {};
|
|
695
|
+
__export(client_exports, {
|
|
696
|
+
NeusClient: () => NeusClient,
|
|
697
|
+
constructVerificationMessage: () => constructVerificationMessage
|
|
698
|
+
});
|
|
699
|
+
var validateVerifierData, NeusClient;
|
|
700
|
+
var init_client = __esm({
|
|
701
|
+
"client.js"() {
|
|
702
|
+
init_errors();
|
|
703
|
+
init_utils();
|
|
704
|
+
validateVerifierData = (verifierId, data) => {
|
|
705
|
+
if (!data || typeof data !== "object") {
|
|
706
|
+
return { valid: false, error: "Data object is required" };
|
|
707
|
+
}
|
|
708
|
+
const ownerField = verifierId === "nft-ownership" || verifierId === "token-holding" ? "ownerAddress" : "owner";
|
|
709
|
+
if (data[ownerField] && !validateWalletAddress(data[ownerField])) {
|
|
710
|
+
return { valid: false, error: `Invalid ${ownerField} address` };
|
|
711
|
+
}
|
|
712
|
+
switch (verifierId) {
|
|
713
|
+
case "ownership-basic":
|
|
714
|
+
if (!data.owner || !validateWalletAddress(data.owner)) {
|
|
715
|
+
return { valid: false, error: "owner (wallet address) is required" };
|
|
716
|
+
}
|
|
717
|
+
if (data.reference !== void 0) {
|
|
718
|
+
if (!data.reference || typeof data.reference !== "object") {
|
|
719
|
+
return { valid: false, error: "reference must be an object when provided" };
|
|
720
|
+
}
|
|
721
|
+
if (!data.reference.type || typeof data.reference.type !== "string") {
|
|
722
|
+
return { valid: false, error: "reference.type is required when reference is provided" };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (!data.content && !data.contentHash) {
|
|
726
|
+
if (!data.reference || typeof data.reference !== "object") {
|
|
727
|
+
return { valid: false, error: "reference is required when neither content nor contentHash is provided" };
|
|
728
|
+
}
|
|
729
|
+
if (!data.reference.id || typeof data.reference.id !== "string") {
|
|
730
|
+
return { valid: false, error: "reference.id is required when neither content nor contentHash is provided" };
|
|
731
|
+
}
|
|
732
|
+
if (!data.reference.type || typeof data.reference.type !== "string") {
|
|
733
|
+
return { valid: false, error: "reference.type is required when neither content nor contentHash is provided" };
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
break;
|
|
737
|
+
case "nft-ownership":
|
|
738
|
+
if (!data.contractAddress || data.tokenId === null || data.tokenId === void 0 || typeof data.chainId !== "number") {
|
|
739
|
+
return { valid: false, error: "contractAddress, tokenId, and chainId are required" };
|
|
740
|
+
}
|
|
741
|
+
if (data.tokenType !== void 0 && data.tokenType !== null) {
|
|
742
|
+
const tt = String(data.tokenType).toLowerCase();
|
|
743
|
+
if (tt !== "erc721" && tt !== "erc1155") {
|
|
744
|
+
return { valid: false, error: "tokenType must be one of: erc721, erc1155" };
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (data.blockNumber !== void 0 && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
|
|
748
|
+
return { valid: false, error: "blockNumber must be an integer when provided" };
|
|
749
|
+
}
|
|
750
|
+
if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
|
|
751
|
+
return { valid: false, error: "Invalid ownerAddress" };
|
|
752
|
+
}
|
|
753
|
+
if (!validateWalletAddress(data.contractAddress)) {
|
|
754
|
+
return { valid: false, error: "Invalid contractAddress" };
|
|
755
|
+
}
|
|
756
|
+
break;
|
|
757
|
+
case "token-holding":
|
|
758
|
+
if (!data.contractAddress || data.minBalance === null || data.minBalance === void 0 || typeof data.chainId !== "number") {
|
|
759
|
+
return { valid: false, error: "contractAddress, minBalance, and chainId are required" };
|
|
760
|
+
}
|
|
761
|
+
if (data.blockNumber !== void 0 && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
|
|
762
|
+
return { valid: false, error: "blockNumber must be an integer when provided" };
|
|
763
|
+
}
|
|
764
|
+
if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
|
|
765
|
+
return { valid: false, error: "Invalid ownerAddress" };
|
|
766
|
+
}
|
|
767
|
+
if (!validateWalletAddress(data.contractAddress)) {
|
|
768
|
+
return { valid: false, error: "Invalid contractAddress" };
|
|
769
|
+
}
|
|
770
|
+
break;
|
|
771
|
+
case "ownership-dns-txt":
|
|
772
|
+
if (!data.domain || typeof data.domain !== "string") {
|
|
773
|
+
return { valid: false, error: "domain is required" };
|
|
774
|
+
}
|
|
775
|
+
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
776
|
+
return { valid: false, error: "Invalid walletAddress" };
|
|
777
|
+
}
|
|
778
|
+
break;
|
|
779
|
+
case "wallet-link":
|
|
780
|
+
if (!data.primaryWalletAddress || !validateWalletAddress(data.primaryWalletAddress)) {
|
|
781
|
+
return { valid: false, error: "primaryWalletAddress is required" };
|
|
782
|
+
}
|
|
783
|
+
if (!data.secondaryWalletAddress || !validateWalletAddress(data.secondaryWalletAddress)) {
|
|
784
|
+
return { valid: false, error: "secondaryWalletAddress is required" };
|
|
785
|
+
}
|
|
786
|
+
if (!data.signature || typeof data.signature !== "string") {
|
|
787
|
+
return { valid: false, error: "signature is required (signed by secondary wallet)" };
|
|
788
|
+
}
|
|
789
|
+
if (typeof data.chainId !== "number") {
|
|
790
|
+
return { valid: false, error: "chainId is required" };
|
|
791
|
+
}
|
|
792
|
+
if (typeof data.signedTimestamp !== "number") {
|
|
793
|
+
return { valid: false, error: "signedTimestamp is required" };
|
|
794
|
+
}
|
|
795
|
+
break;
|
|
796
|
+
case "contract-ownership":
|
|
797
|
+
if (!data.contractAddress || !validateWalletAddress(data.contractAddress)) {
|
|
798
|
+
return { valid: false, error: "contractAddress is required" };
|
|
799
|
+
}
|
|
800
|
+
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
801
|
+
return { valid: false, error: "Invalid walletAddress" };
|
|
802
|
+
}
|
|
803
|
+
if (typeof data.chainId !== "number") {
|
|
804
|
+
return { valid: false, error: "chainId is required" };
|
|
805
|
+
}
|
|
806
|
+
break;
|
|
807
|
+
case "agent-identity":
|
|
808
|
+
if (!data.agentId || typeof data.agentId !== "string" || data.agentId.length < 1 || data.agentId.length > 128) {
|
|
809
|
+
return { valid: false, error: "agentId is required (1-128 chars)" };
|
|
810
|
+
}
|
|
811
|
+
if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
|
|
812
|
+
return { valid: false, error: "agentWallet is required" };
|
|
813
|
+
}
|
|
814
|
+
if (data.agentType && !["ai", "bot", "service", "automation", "agent"].includes(data.agentType)) {
|
|
815
|
+
return { valid: false, error: "agentType must be one of: ai, bot, service, automation, agent" };
|
|
816
|
+
}
|
|
817
|
+
break;
|
|
818
|
+
case "agent-delegation":
|
|
819
|
+
if (!data.controllerWallet || !validateWalletAddress(data.controllerWallet)) {
|
|
820
|
+
return { valid: false, error: "controllerWallet is required" };
|
|
821
|
+
}
|
|
822
|
+
if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
|
|
823
|
+
return { valid: false, error: "agentWallet is required" };
|
|
824
|
+
}
|
|
825
|
+
if (data.scope && (typeof data.scope !== "string" || data.scope.length > 128)) {
|
|
826
|
+
return { valid: false, error: "scope must be a string (max 128 chars)" };
|
|
827
|
+
}
|
|
828
|
+
if (data.expiresAt && (typeof data.expiresAt !== "number" || data.expiresAt < Date.now())) {
|
|
829
|
+
return { valid: false, error: "expiresAt must be a future timestamp" };
|
|
830
|
+
}
|
|
831
|
+
break;
|
|
832
|
+
case "ai-content-moderation":
|
|
833
|
+
if (!data.content || typeof data.content !== "string") {
|
|
834
|
+
return { valid: false, error: "content is required" };
|
|
835
|
+
}
|
|
836
|
+
if (!data.contentType || typeof data.contentType !== "string") {
|
|
837
|
+
return { valid: false, error: "contentType (MIME type) is required" };
|
|
838
|
+
}
|
|
839
|
+
{
|
|
840
|
+
const contentType = String(data.contentType).split(";")[0].trim().toLowerCase();
|
|
841
|
+
const validTypes = [
|
|
842
|
+
"image/jpeg",
|
|
843
|
+
"image/png",
|
|
844
|
+
"image/webp",
|
|
845
|
+
"image/gif",
|
|
846
|
+
"text/plain",
|
|
847
|
+
"text/markdown",
|
|
848
|
+
"text/x-markdown",
|
|
849
|
+
"application/json",
|
|
850
|
+
"application/xml"
|
|
851
|
+
];
|
|
852
|
+
if (!validTypes.includes(contentType)) {
|
|
853
|
+
return { valid: false, error: `contentType must be one of: ${validTypes.join(", ")}` };
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (data.content.length > 13653334) {
|
|
857
|
+
return { valid: false, error: "content exceeds 10MB limit" };
|
|
858
|
+
}
|
|
859
|
+
break;
|
|
860
|
+
case "ownership-pseudonym":
|
|
861
|
+
if (!data.pseudonymId || typeof data.pseudonymId !== "string") {
|
|
862
|
+
return { valid: false, error: "pseudonymId is required" };
|
|
863
|
+
}
|
|
864
|
+
if (!/^[a-z0-9._-]{3,64}$/.test(data.pseudonymId.trim().toLowerCase())) {
|
|
865
|
+
return { valid: false, error: "pseudonymId must be 3-64 characters, lowercase alphanumeric with dots, underscores, or hyphens" };
|
|
866
|
+
}
|
|
867
|
+
if (data.namespace && typeof data.namespace === "string") {
|
|
868
|
+
if (!/^[a-z0-9._-]{1,64}$/.test(data.namespace.trim().toLowerCase())) {
|
|
869
|
+
return { valid: false, error: "namespace must be 1-64 characters, lowercase alphanumeric with dots, underscores, or hyphens" };
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
break;
|
|
873
|
+
case "wallet-risk":
|
|
874
|
+
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
875
|
+
return { valid: false, error: "Invalid walletAddress" };
|
|
876
|
+
}
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
return { valid: true };
|
|
880
|
+
};
|
|
881
|
+
NeusClient = class {
|
|
882
|
+
constructor(config = {}) {
|
|
883
|
+
this.config = {
|
|
884
|
+
timeout: 3e4,
|
|
885
|
+
enableLogging: false,
|
|
886
|
+
...config
|
|
887
|
+
};
|
|
888
|
+
this.baseUrl = this.config.apiUrl || "https://api.neus.network";
|
|
889
|
+
try {
|
|
890
|
+
const url = new URL(this.baseUrl);
|
|
891
|
+
if (url.hostname.endsWith("neus.network") && url.protocol === "http:") {
|
|
892
|
+
url.protocol = "https:";
|
|
893
|
+
}
|
|
894
|
+
this.baseUrl = url.toString().replace(/\/$/, "");
|
|
895
|
+
} catch {
|
|
896
|
+
}
|
|
897
|
+
this.config.apiUrl = this.baseUrl;
|
|
898
|
+
this.defaultHeaders = {
|
|
899
|
+
"Content-Type": "application/json",
|
|
900
|
+
"Accept": "application/json",
|
|
901
|
+
"X-Neus-Sdk": "js"
|
|
902
|
+
};
|
|
903
|
+
if (typeof this.config.apiKey === "string" && this.config.apiKey.trim().length > 0) {
|
|
904
|
+
this.defaultHeaders["Authorization"] = `Bearer ${this.config.apiKey.trim()}`;
|
|
905
|
+
}
|
|
906
|
+
try {
|
|
907
|
+
if (typeof window !== "undefined" && window.location && window.location.origin) {
|
|
908
|
+
this.defaultHeaders["X-Client-Origin"] = window.location.origin;
|
|
909
|
+
}
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
// ============================================================================
|
|
914
|
+
// CORE VERIFICATION METHODS
|
|
915
|
+
// ============================================================================
|
|
916
|
+
/**
|
|
917
|
+
* VERIFY - Standard verification (auto or manual)
|
|
918
|
+
*
|
|
919
|
+
* Create proofs with complete control over the verification process.
|
|
920
|
+
* If signature and walletAddress are omitted but verifier/content are provided,
|
|
921
|
+
* this method performs the wallet flow inline (no aliases, no secondary methods).
|
|
922
|
+
*
|
|
923
|
+
* @param {Object} params - Verification parameters
|
|
924
|
+
* @param {Array<string>} [params.verifierIds] - Array of verifier IDs (manual path)
|
|
925
|
+
* @param {Object} [params.data] - Verification data object (manual path)
|
|
926
|
+
* @param {string} [params.walletAddress] - Wallet address that signed the request (manual path)
|
|
927
|
+
* @param {string} [params.signature] - EIP-191 signature (manual path)
|
|
928
|
+
* @param {number} [params.signedTimestamp] - Unix timestamp when signature was created (manual path)
|
|
929
|
+
* @param {number} [params.chainId] - Chain ID for verification context (optional, managed by protocol)
|
|
930
|
+
* @param {Object} [params.options] - Additional options
|
|
931
|
+
* @param {string} [params.verifier] - Verifier ID (auto path)
|
|
932
|
+
* @param {string} [params.content] - Content/description (auto path)
|
|
933
|
+
* @param {Object} [params.wallet] - Optional injected wallet/provider (auto path)
|
|
934
|
+
* @returns {Promise<Object>} Verification result with qHash
|
|
935
|
+
*
|
|
936
|
+
* @example
|
|
937
|
+
* const proof = await client.verify({
|
|
938
|
+
* verifierIds: ['ownership-basic'],
|
|
939
|
+
* data: {
|
|
940
|
+
* content: "My content",
|
|
941
|
+
* owner: walletAddress, // or ownerAddress for nft-ownership/token-holding
|
|
942
|
+
* reference: { type: 'content', id: 'my-unique-identifier' }
|
|
943
|
+
* },
|
|
944
|
+
* walletAddress: '0x...',
|
|
945
|
+
* signature: '0x...',
|
|
946
|
+
* signedTimestamp: Date.now(),
|
|
947
|
+
* options: { targetChains: [421614, 11155111] }
|
|
948
|
+
* });
|
|
949
|
+
*/
|
|
950
|
+
/**
|
|
951
|
+
* Create a verification proof
|
|
952
|
+
*
|
|
953
|
+
* @param {Object} params - Verification parameters
|
|
954
|
+
* @param {string} [params.verifier] - Verifier ID (e.g., 'ownership-basic')
|
|
955
|
+
* @param {string} [params.content] - Content to verify
|
|
956
|
+
* @param {Object} [params.data] - Structured verification data
|
|
957
|
+
* @param {Object} [params.wallet] - Wallet provider
|
|
958
|
+
* @param {Object} [params.options] - Additional options
|
|
959
|
+
* @returns {Promise<Object>} Verification result with qHash
|
|
960
|
+
*
|
|
961
|
+
* @example
|
|
962
|
+
* // Simple ownership proof
|
|
963
|
+
* const proof = await client.verify({
|
|
964
|
+
* verifier: 'ownership-basic',
|
|
965
|
+
* content: 'Hello World',
|
|
966
|
+
* wallet: window.ethereum
|
|
967
|
+
* });
|
|
968
|
+
*/
|
|
969
|
+
async verify(params) {
|
|
970
|
+
if ((!params?.signature || !params?.walletAddress) && (params?.verifier || params?.content || params?.data)) {
|
|
971
|
+
const { content, verifier = "ownership-basic", data: data2 = null, wallet = null, options: options2 = {} } = params;
|
|
972
|
+
if (verifier === "ownership-basic" && !data2 && (!content || typeof content !== "string")) {
|
|
973
|
+
throw new ValidationError("content is required and must be a string (or use data param with owner + reference)");
|
|
974
|
+
}
|
|
975
|
+
const validVerifiers = [
|
|
976
|
+
"ownership-basic",
|
|
977
|
+
"ownership-pseudonym",
|
|
978
|
+
// Pseudonymous identity (public)
|
|
979
|
+
"nft-ownership",
|
|
980
|
+
"token-holding",
|
|
981
|
+
"ownership-dns-txt",
|
|
982
|
+
"wallet-link",
|
|
983
|
+
"contract-ownership",
|
|
984
|
+
"wallet-risk",
|
|
985
|
+
// Wallet risk assessment (public)
|
|
986
|
+
// AI & Agent verifiers (ERC-8004 aligned)
|
|
987
|
+
"agent-identity",
|
|
988
|
+
"agent-delegation",
|
|
989
|
+
"ai-content-moderation"
|
|
990
|
+
];
|
|
991
|
+
if (!validVerifiers.includes(verifier)) {
|
|
992
|
+
throw new ValidationError(`Invalid verifier '${verifier}'. Must be one of: ${validVerifiers.join(", ")}.`);
|
|
993
|
+
}
|
|
994
|
+
const requiresDataParam = [
|
|
995
|
+
"ownership-dns-txt",
|
|
996
|
+
"wallet-link",
|
|
997
|
+
"contract-ownership",
|
|
998
|
+
"ownership-pseudonym",
|
|
999
|
+
"wallet-risk",
|
|
1000
|
+
"agent-identity",
|
|
1001
|
+
"agent-delegation",
|
|
1002
|
+
"ai-content-moderation"
|
|
1003
|
+
];
|
|
1004
|
+
if (requiresDataParam.includes(verifier)) {
|
|
1005
|
+
if (!data2 || typeof data2 !== "object") {
|
|
1006
|
+
throw new ValidationError(`${verifier} requires explicit data parameter. Cannot use auto-path.`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
let walletAddress2, provider;
|
|
1010
|
+
if (wallet) {
|
|
1011
|
+
walletAddress2 = wallet.address || wallet.selectedAddress;
|
|
1012
|
+
provider = wallet.provider || wallet;
|
|
1013
|
+
} else {
|
|
1014
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
1015
|
+
throw new ConfigurationError("No Web3 wallet detected. Please install MetaMask or provide wallet parameter.");
|
|
1016
|
+
}
|
|
1017
|
+
await window.ethereum.request({ method: "eth_requestAccounts" });
|
|
1018
|
+
provider = window.ethereum;
|
|
1019
|
+
const accounts = await provider.request({ method: "eth_accounts" });
|
|
1020
|
+
walletAddress2 = accounts[0];
|
|
1021
|
+
}
|
|
1022
|
+
let verificationData;
|
|
1023
|
+
if (verifier === "ownership-basic") {
|
|
1024
|
+
if (data2 && typeof data2 === "object") {
|
|
1025
|
+
verificationData = {
|
|
1026
|
+
owner: data2.owner || walletAddress2,
|
|
1027
|
+
reference: data2.reference,
|
|
1028
|
+
...data2.content && { content: data2.content },
|
|
1029
|
+
...data2.contentHash && { contentHash: data2.contentHash },
|
|
1030
|
+
...data2.provenance && { provenance: data2.provenance }
|
|
1031
|
+
};
|
|
1032
|
+
} else {
|
|
1033
|
+
verificationData = {
|
|
1034
|
+
content,
|
|
1035
|
+
owner: walletAddress2,
|
|
1036
|
+
reference: { type: "other" }
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
} else if (verifier === "token-holding") {
|
|
1040
|
+
if (!data2?.contractAddress) {
|
|
1041
|
+
throw new ValidationError("token-holding requires contractAddress in data parameter");
|
|
1042
|
+
}
|
|
1043
|
+
if (data2?.minBalance === null || data2?.minBalance === void 0) {
|
|
1044
|
+
throw new ValidationError("token-holding requires minBalance in data parameter");
|
|
1045
|
+
}
|
|
1046
|
+
if (typeof data2?.chainId !== "number") {
|
|
1047
|
+
throw new ValidationError("token-holding requires chainId (number) in data parameter");
|
|
1048
|
+
}
|
|
1049
|
+
verificationData = {
|
|
1050
|
+
ownerAddress: walletAddress2,
|
|
1051
|
+
contractAddress: data2.contractAddress,
|
|
1052
|
+
minBalance: data2.minBalance,
|
|
1053
|
+
chainId: data2.chainId
|
|
1054
|
+
};
|
|
1055
|
+
} else if (verifier === "nft-ownership") {
|
|
1056
|
+
if (!data2?.contractAddress) {
|
|
1057
|
+
throw new ValidationError("nft-ownership requires contractAddress in data parameter");
|
|
1058
|
+
}
|
|
1059
|
+
if (data2?.tokenId === null || data2?.tokenId === void 0) {
|
|
1060
|
+
throw new ValidationError("nft-ownership requires tokenId in data parameter");
|
|
1061
|
+
}
|
|
1062
|
+
if (typeof data2?.chainId !== "number") {
|
|
1063
|
+
throw new ValidationError("nft-ownership requires chainId (number) in data parameter");
|
|
1064
|
+
}
|
|
1065
|
+
verificationData = {
|
|
1066
|
+
ownerAddress: walletAddress2,
|
|
1067
|
+
contractAddress: data2.contractAddress,
|
|
1068
|
+
tokenId: data2.tokenId,
|
|
1069
|
+
chainId: data2.chainId,
|
|
1070
|
+
tokenType: data2?.tokenType || "erc721"
|
|
1071
|
+
};
|
|
1072
|
+
} else if (verifier === "ownership-dns-txt") {
|
|
1073
|
+
if (!data2?.domain) {
|
|
1074
|
+
throw new ValidationError("ownership-dns-txt requires domain in data parameter");
|
|
1075
|
+
}
|
|
1076
|
+
verificationData = {
|
|
1077
|
+
domain: data2.domain,
|
|
1078
|
+
walletAddress: walletAddress2
|
|
1079
|
+
};
|
|
1080
|
+
} else if (verifier === "wallet-link") {
|
|
1081
|
+
if (!data2?.secondaryWalletAddress) {
|
|
1082
|
+
throw new ValidationError("wallet-link requires secondaryWalletAddress in data parameter");
|
|
1083
|
+
}
|
|
1084
|
+
if (!data2?.signature) {
|
|
1085
|
+
throw new ValidationError("wallet-link requires signature in data parameter (signed by secondary wallet)");
|
|
1086
|
+
}
|
|
1087
|
+
if (typeof data2?.chainId !== "number") {
|
|
1088
|
+
throw new ValidationError("wallet-link requires chainId (number) in data parameter");
|
|
1089
|
+
}
|
|
1090
|
+
verificationData = {
|
|
1091
|
+
primaryWalletAddress: walletAddress2,
|
|
1092
|
+
secondaryWalletAddress: data2.secondaryWalletAddress,
|
|
1093
|
+
signature: data2.signature,
|
|
1094
|
+
chainId: data2.chainId,
|
|
1095
|
+
signedTimestamp: data2?.signedTimestamp || Date.now()
|
|
1096
|
+
};
|
|
1097
|
+
} else if (verifier === "contract-ownership") {
|
|
1098
|
+
if (!data2?.contractAddress) {
|
|
1099
|
+
throw new ValidationError("contract-ownership requires contractAddress in data parameter");
|
|
1100
|
+
}
|
|
1101
|
+
if (typeof data2?.chainId !== "number") {
|
|
1102
|
+
throw new ValidationError("contract-ownership requires chainId (number) in data parameter");
|
|
1103
|
+
}
|
|
1104
|
+
verificationData = {
|
|
1105
|
+
contractAddress: data2.contractAddress,
|
|
1106
|
+
walletAddress: walletAddress2,
|
|
1107
|
+
chainId: data2.chainId,
|
|
1108
|
+
...data2?.method && { method: data2.method }
|
|
1109
|
+
};
|
|
1110
|
+
} else if (verifier === "agent-identity") {
|
|
1111
|
+
if (!data2?.agentId) {
|
|
1112
|
+
throw new ValidationError("agent-identity requires agentId in data parameter");
|
|
1113
|
+
}
|
|
1114
|
+
verificationData = {
|
|
1115
|
+
agentId: data2.agentId,
|
|
1116
|
+
agentWallet: data2?.agentWallet || walletAddress2,
|
|
1117
|
+
...data2?.agentLabel && { agentLabel: data2.agentLabel },
|
|
1118
|
+
...data2?.agentType && { agentType: data2.agentType },
|
|
1119
|
+
...data2?.description && { description: data2.description },
|
|
1120
|
+
...data2?.capabilities && { capabilities: data2.capabilities }
|
|
1121
|
+
};
|
|
1122
|
+
} else if (verifier === "agent-delegation") {
|
|
1123
|
+
if (!data2?.agentWallet) {
|
|
1124
|
+
throw new ValidationError("agent-delegation requires agentWallet in data parameter");
|
|
1125
|
+
}
|
|
1126
|
+
verificationData = {
|
|
1127
|
+
controllerWallet: data2?.controllerWallet || walletAddress2,
|
|
1128
|
+
agentWallet: data2.agentWallet,
|
|
1129
|
+
...data2?.agentId && { agentId: data2.agentId },
|
|
1130
|
+
...data2?.scope && { scope: data2.scope },
|
|
1131
|
+
...data2?.permissions && { permissions: data2.permissions },
|
|
1132
|
+
...data2?.maxSpend && { maxSpend: data2.maxSpend },
|
|
1133
|
+
...data2?.expiresAt && { expiresAt: data2.expiresAt }
|
|
1134
|
+
};
|
|
1135
|
+
} else if (verifier === "ai-content-moderation") {
|
|
1136
|
+
if (!data2?.content) {
|
|
1137
|
+
throw new ValidationError("ai-content-moderation requires content (base64) in data parameter");
|
|
1138
|
+
}
|
|
1139
|
+
if (!data2?.contentType) {
|
|
1140
|
+
throw new ValidationError("ai-content-moderation requires contentType (MIME type) in data parameter");
|
|
1141
|
+
}
|
|
1142
|
+
verificationData = {
|
|
1143
|
+
content: data2.content,
|
|
1144
|
+
contentType: data2.contentType,
|
|
1145
|
+
...data2?.provider && { provider: data2.provider }
|
|
1146
|
+
};
|
|
1147
|
+
} else if (verifier === "ownership-pseudonym") {
|
|
1148
|
+
if (!data2?.pseudonymId) {
|
|
1149
|
+
throw new ValidationError("ownership-pseudonym requires pseudonymId in data parameter");
|
|
1150
|
+
}
|
|
1151
|
+
verificationData = {
|
|
1152
|
+
pseudonymId: data2.pseudonymId,
|
|
1153
|
+
...data2?.namespace && { namespace: data2.namespace },
|
|
1154
|
+
...data2?.displayName && { displayName: data2.displayName },
|
|
1155
|
+
...data2?.metadata && { metadata: data2.metadata }
|
|
1156
|
+
};
|
|
1157
|
+
} else if (verifier === "wallet-risk") {
|
|
1158
|
+
verificationData = {
|
|
1159
|
+
walletAddress: data2?.walletAddress || walletAddress2,
|
|
1160
|
+
...data2?.provider && { provider: data2.provider },
|
|
1161
|
+
// Mainnet-first semantics: if caller doesn't provide chainId, default to Ethereum mainnet (1).
|
|
1162
|
+
// This avoids accidental testnet semantics for risk providers.
|
|
1163
|
+
chainId: typeof data2?.chainId === "number" && Number.isFinite(data2.chainId) ? data2.chainId : 1,
|
|
1164
|
+
...data2?.includeDetails !== void 0 && { includeDetails: data2.includeDetails }
|
|
1165
|
+
};
|
|
1166
|
+
} else {
|
|
1167
|
+
verificationData = data2 ? {
|
|
1168
|
+
content,
|
|
1169
|
+
owner: walletAddress2,
|
|
1170
|
+
...data2
|
|
1171
|
+
} : {
|
|
1172
|
+
content,
|
|
1173
|
+
owner: walletAddress2
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
const signedTimestamp2 = Date.now();
|
|
1177
|
+
const verifierIds2 = [verifier];
|
|
1178
|
+
const message = constructVerificationMessage({
|
|
1179
|
+
walletAddress: walletAddress2,
|
|
1180
|
+
signedTimestamp: signedTimestamp2,
|
|
1181
|
+
data: verificationData,
|
|
1182
|
+
verifierIds: verifierIds2,
|
|
1183
|
+
chainId: NEUS_CONSTANTS.HUB_CHAIN_ID
|
|
1184
|
+
// Protocol-managed chain
|
|
1185
|
+
});
|
|
1186
|
+
let signature2;
|
|
1187
|
+
try {
|
|
1188
|
+
const toHexUtf8 = (s) => {
|
|
1189
|
+
try {
|
|
1190
|
+
const enc = new TextEncoder();
|
|
1191
|
+
const bytes = enc.encode(s);
|
|
1192
|
+
let hex = "0x";
|
|
1193
|
+
for (let i = 0; i < bytes.length; i++)
|
|
1194
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
1195
|
+
return hex;
|
|
1196
|
+
} catch {
|
|
1197
|
+
let hex = "0x";
|
|
1198
|
+
for (let i = 0; i < s.length; i++)
|
|
1199
|
+
hex += s.charCodeAt(i).toString(16).padStart(2, "0");
|
|
1200
|
+
return hex;
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
const isFarcasterWallet = (() => {
|
|
1204
|
+
if (typeof window === "undefined")
|
|
1205
|
+
return false;
|
|
1206
|
+
try {
|
|
1207
|
+
const w = window;
|
|
1208
|
+
const fc = w.farcaster;
|
|
1209
|
+
if (!fc || !fc.context)
|
|
1210
|
+
return false;
|
|
1211
|
+
const fcProvider = fc.provider || fc.walletProvider || fc.context && fc.context.walletProvider;
|
|
1212
|
+
if (fcProvider === provider)
|
|
1213
|
+
return true;
|
|
1214
|
+
if (w.mini && w.mini.wallet === provider && fc && fc.context)
|
|
1215
|
+
return true;
|
|
1216
|
+
if (w.ethereum === provider && fc && fc.context)
|
|
1217
|
+
return true;
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
return false;
|
|
1221
|
+
})();
|
|
1222
|
+
if (isFarcasterWallet) {
|
|
1223
|
+
try {
|
|
1224
|
+
const hexMsg = toHexUtf8(message);
|
|
1225
|
+
signature2 = await provider.request({ method: "personal_sign", params: [hexMsg, walletAddress2] });
|
|
1226
|
+
} catch (e) {
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (!signature2) {
|
|
1230
|
+
try {
|
|
1231
|
+
signature2 = await provider.request({ method: "personal_sign", params: [message, walletAddress2] });
|
|
1232
|
+
} catch (e) {
|
|
1233
|
+
const msg = String(e && (e.message || e.reason) || e || "").toLowerCase();
|
|
1234
|
+
const errCode = e && (e.code || e.error && e.error.code) || null;
|
|
1235
|
+
const needsHex = /byte|bytes|invalid byte sequence|encoding|non-hex/i.test(msg);
|
|
1236
|
+
const methodUnsupported = /method.*not.*supported|unsupported|not implemented|method not found|unknown method|does not support/i.test(msg) || errCode === -32601 || errCode === 4200 || msg.includes("personal_sign") && msg.includes("not") || msg.includes("request method") && msg.includes("not supported");
|
|
1237
|
+
if (methodUnsupported) {
|
|
1238
|
+
this._log("personal_sign not supported; attempting eth_sign fallback");
|
|
1239
|
+
try {
|
|
1240
|
+
const enc = new TextEncoder();
|
|
1241
|
+
const bytes = enc.encode(message);
|
|
1242
|
+
const prefix = `Ethereum Signed Message:
|
|
1243
|
+
${bytes.length}`;
|
|
1244
|
+
const full = new Uint8Array(prefix.length + bytes.length);
|
|
1245
|
+
for (let i = 0; i < prefix.length; i++)
|
|
1246
|
+
full[i] = prefix.charCodeAt(i);
|
|
1247
|
+
full.set(bytes, prefix.length);
|
|
1248
|
+
let payloadHex = "0x";
|
|
1249
|
+
for (let i = 0; i < full.length; i++)
|
|
1250
|
+
payloadHex += full[i].toString(16).padStart(2, "0");
|
|
1251
|
+
try {
|
|
1252
|
+
if (typeof window !== "undefined")
|
|
1253
|
+
window.__NEUS_ALLOW_ETH_SIGN__ = true;
|
|
1254
|
+
} catch {
|
|
1255
|
+
}
|
|
1256
|
+
signature2 = await provider.request({ method: "eth_sign", params: [walletAddress2, payloadHex], neusAllowEthSign: true });
|
|
1257
|
+
try {
|
|
1258
|
+
if (typeof window !== "undefined")
|
|
1259
|
+
delete window.__NEUS_ALLOW_ETH_SIGN__;
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
} catch (fallbackErr) {
|
|
1263
|
+
this._log("eth_sign fallback failed", { message: fallbackErr?.message || String(fallbackErr) });
|
|
1264
|
+
try {
|
|
1265
|
+
if (typeof window !== "undefined")
|
|
1266
|
+
delete window.__NEUS_ALLOW_ETH_SIGN__;
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
throw e;
|
|
1270
|
+
}
|
|
1271
|
+
} else if (needsHex) {
|
|
1272
|
+
this._log("Retrying personal_sign with hex-encoded message");
|
|
1273
|
+
const hexMsg = toHexUtf8(message);
|
|
1274
|
+
signature2 = await provider.request({ method: "personal_sign", params: [hexMsg, walletAddress2] });
|
|
1275
|
+
} else {
|
|
1276
|
+
throw e;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
if (error.code === 4001) {
|
|
1282
|
+
throw new ValidationError("User rejected the signature request. Signature is required to create proofs.");
|
|
1283
|
+
}
|
|
1284
|
+
throw new ValidationError(`Failed to sign verification message: ${error.message}`);
|
|
1285
|
+
}
|
|
1286
|
+
return this.verify({
|
|
1287
|
+
verifierIds: verifierIds2,
|
|
1288
|
+
data: verificationData,
|
|
1289
|
+
walletAddress: walletAddress2,
|
|
1290
|
+
signature: signature2,
|
|
1291
|
+
signedTimestamp: signedTimestamp2,
|
|
1292
|
+
options: options2
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
const {
|
|
1296
|
+
verifierIds,
|
|
1297
|
+
data,
|
|
1298
|
+
walletAddress,
|
|
1299
|
+
signature,
|
|
1300
|
+
signedTimestamp,
|
|
1301
|
+
chainId,
|
|
1302
|
+
chain,
|
|
1303
|
+
signatureMethod,
|
|
1304
|
+
options = {}
|
|
1305
|
+
} = params;
|
|
1306
|
+
const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
|
|
1307
|
+
const normalizeVerifierId = (id) => {
|
|
1308
|
+
if (typeof id !== "string")
|
|
1309
|
+
return id;
|
|
1310
|
+
const match = id.match(/^(.*)@\d+$/);
|
|
1311
|
+
return match ? match[1] : id;
|
|
1312
|
+
};
|
|
1313
|
+
const normalizedVerifierIds = Array.isArray(verifierIds) ? verifierIds.map(normalizeVerifierId) : [];
|
|
1314
|
+
if (!normalizedVerifierIds || normalizedVerifierIds.length === 0) {
|
|
1315
|
+
throw new ValidationError("verifierIds array is required");
|
|
1316
|
+
}
|
|
1317
|
+
if (!data || typeof data !== "object") {
|
|
1318
|
+
throw new ValidationError("data object is required");
|
|
1319
|
+
}
|
|
1320
|
+
if (!walletAddress || typeof walletAddress !== "string") {
|
|
1321
|
+
throw new ValidationError("walletAddress is required");
|
|
1322
|
+
}
|
|
1323
|
+
if (!signature) {
|
|
1324
|
+
throw new ValidationError("signature is required");
|
|
1325
|
+
}
|
|
1326
|
+
if (!signedTimestamp || typeof signedTimestamp !== "number") {
|
|
1327
|
+
throw new ValidationError("signedTimestamp is required");
|
|
1328
|
+
}
|
|
1329
|
+
if (resolvedChainId !== null && typeof resolvedChainId !== "number") {
|
|
1330
|
+
throw new ValidationError("chainId must be a number");
|
|
1331
|
+
}
|
|
1332
|
+
for (const verifierId of normalizedVerifierIds) {
|
|
1333
|
+
const validation = validateVerifierData(verifierId, data);
|
|
1334
|
+
if (!validation.valid) {
|
|
1335
|
+
throw new ValidationError(`Validation failed for verifier '${verifierId}': ${validation.error}`);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
const optionsPayload = {
|
|
1339
|
+
...options && typeof options === "object" ? options : {},
|
|
1340
|
+
targetChains: Array.isArray(options?.targetChains) ? options.targetChains : [],
|
|
1341
|
+
// Privacy and storage options (defaults)
|
|
1342
|
+
privacyLevel: options?.privacyLevel || "private",
|
|
1343
|
+
publicDisplay: options?.publicDisplay || false,
|
|
1344
|
+
storeOriginalContent: options?.storeOriginalContent || false
|
|
1345
|
+
};
|
|
1346
|
+
if (typeof options?.enableIpfs === "boolean")
|
|
1347
|
+
optionsPayload.enableIpfs = options.enableIpfs;
|
|
1348
|
+
const requestData = {
|
|
1349
|
+
verifierIds: normalizedVerifierIds,
|
|
1350
|
+
data,
|
|
1351
|
+
walletAddress,
|
|
1352
|
+
signature,
|
|
1353
|
+
signedTimestamp,
|
|
1354
|
+
...resolvedChainId !== null && { chainId: resolvedChainId },
|
|
1355
|
+
...chain && { chain },
|
|
1356
|
+
...signatureMethod && { signatureMethod },
|
|
1357
|
+
options: optionsPayload
|
|
1358
|
+
};
|
|
1359
|
+
const response = await this._makeRequest("POST", "/api/v1/verification", requestData);
|
|
1360
|
+
if (!response.success) {
|
|
1361
|
+
throw new ApiError(`Verification failed: ${response.error?.message || "Unknown error"}`, response.error);
|
|
1362
|
+
}
|
|
1363
|
+
return this._formatResponse(response);
|
|
1364
|
+
}
|
|
1365
|
+
// ============================================================================
|
|
1366
|
+
// STATUS AND UTILITY METHODS
|
|
1367
|
+
// ============================================================================
|
|
1368
|
+
/**
|
|
1369
|
+
* Get verification status
|
|
1370
|
+
*
|
|
1371
|
+
* @param {string} qHash - Verification ID (qHash or proofId)
|
|
1372
|
+
* @returns {Promise<Object>} Verification status and data
|
|
1373
|
+
*
|
|
1374
|
+
* @example
|
|
1375
|
+
* const result = await client.getStatus('0x...');
|
|
1376
|
+
* console.log('Status:', result.status);
|
|
1377
|
+
*/
|
|
1378
|
+
async getStatus(qHash) {
|
|
1379
|
+
if (!qHash || typeof qHash !== "string") {
|
|
1380
|
+
throw new ValidationError("qHash is required");
|
|
1381
|
+
}
|
|
1382
|
+
const response = await this._makeRequest("GET", `/api/v1/verification/status/${qHash}`);
|
|
1383
|
+
if (!response.success) {
|
|
1384
|
+
throw new ApiError(`Failed to get status: ${response.error?.message || "Unknown error"}`, response.error);
|
|
1385
|
+
}
|
|
1386
|
+
return this._formatResponse(response);
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Get private proof status with wallet signature
|
|
1390
|
+
*
|
|
1391
|
+
* @param {string} qHash - Verification ID
|
|
1392
|
+
* @param {Object} wallet - Wallet provider (window.ethereum or ethers Wallet)
|
|
1393
|
+
* @returns {Promise<Object>} Private verification status and data
|
|
1394
|
+
*
|
|
1395
|
+
* @example
|
|
1396
|
+
* // Access private proof
|
|
1397
|
+
* const privateData = await client.getPrivateStatus(qHash, window.ethereum);
|
|
1398
|
+
*/
|
|
1399
|
+
async getPrivateStatus(qHash, wallet = null) {
|
|
1400
|
+
if (!qHash || typeof qHash !== "string") {
|
|
1401
|
+
throw new ValidationError("qHash is required");
|
|
1402
|
+
}
|
|
1403
|
+
if (!wallet) {
|
|
1404
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
1405
|
+
throw new ConfigurationError("No wallet provider available");
|
|
1406
|
+
}
|
|
1407
|
+
wallet = window.ethereum;
|
|
1408
|
+
}
|
|
1409
|
+
let walletAddress, provider;
|
|
1410
|
+
if (wallet.address) {
|
|
1411
|
+
walletAddress = wallet.address;
|
|
1412
|
+
provider = wallet;
|
|
1413
|
+
} else if (wallet.selectedAddress || wallet.request) {
|
|
1414
|
+
provider = wallet;
|
|
1415
|
+
if (wallet.selectedAddress) {
|
|
1416
|
+
walletAddress = wallet.selectedAddress;
|
|
1417
|
+
} else {
|
|
1418
|
+
const accounts = await provider.request({ method: "eth_accounts" });
|
|
1419
|
+
if (!accounts || accounts.length === 0) {
|
|
1420
|
+
throw new ConfigurationError("No wallet accounts available");
|
|
1421
|
+
}
|
|
1422
|
+
walletAddress = accounts[0];
|
|
1423
|
+
}
|
|
1424
|
+
} else {
|
|
1425
|
+
throw new ConfigurationError("Invalid wallet provider");
|
|
1426
|
+
}
|
|
1427
|
+
const signedTimestamp = Date.now();
|
|
1428
|
+
const message = constructVerificationMessage({
|
|
1429
|
+
walletAddress,
|
|
1430
|
+
signedTimestamp,
|
|
1431
|
+
data: { action: "access_private_proof", qHash },
|
|
1432
|
+
verifierIds: ["ownership-basic"],
|
|
1433
|
+
chainId: NEUS_CONSTANTS.HUB_CHAIN_ID
|
|
1434
|
+
});
|
|
1435
|
+
let signature;
|
|
1436
|
+
try {
|
|
1437
|
+
if (provider.signMessage) {
|
|
1438
|
+
signature = await provider.signMessage(message);
|
|
1439
|
+
} else {
|
|
1440
|
+
signature = await provider.request({
|
|
1441
|
+
method: "personal_sign",
|
|
1442
|
+
params: [message, walletAddress]
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
if (error.code === 4001) {
|
|
1447
|
+
throw new ValidationError("User rejected signature request");
|
|
1448
|
+
}
|
|
1449
|
+
throw new ValidationError(`Failed to sign message: ${error.message}`);
|
|
1450
|
+
}
|
|
1451
|
+
const response = await this._makeRequest("GET", `/api/v1/verification/status/${qHash}`, null, {
|
|
1452
|
+
"x-wallet-address": walletAddress,
|
|
1453
|
+
"x-signature": signature,
|
|
1454
|
+
"x-signed-timestamp": signedTimestamp.toString()
|
|
1455
|
+
});
|
|
1456
|
+
if (!response.success) {
|
|
1457
|
+
throw new ApiError(
|
|
1458
|
+
`Failed to access private proof: ${response.error?.message || "Unauthorized"}`,
|
|
1459
|
+
response.error
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
return this._formatResponse(response);
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Check API health
|
|
1466
|
+
*
|
|
1467
|
+
* @returns {Promise<boolean>} True if API is healthy
|
|
1468
|
+
*/
|
|
1469
|
+
async isHealthy() {
|
|
1470
|
+
try {
|
|
1471
|
+
const response = await this._makeRequest("GET", "/api/v1/health");
|
|
1472
|
+
return response.success === true;
|
|
1473
|
+
} catch {
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* List available verifiers
|
|
1479
|
+
*
|
|
1480
|
+
* @returns {Promise<string[]>} Array of verifier IDs
|
|
1481
|
+
*/
|
|
1482
|
+
async getVerifiers() {
|
|
1483
|
+
const response = await this._makeRequest("GET", "/api/v1/verification/verifiers");
|
|
1484
|
+
if (!response.success) {
|
|
1485
|
+
throw new ApiError(`Failed to get verifiers: ${response.error?.message || "Unknown error"}`, response.error);
|
|
1486
|
+
}
|
|
1487
|
+
return Array.isArray(response.data) ? response.data : [];
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* POLL PROOF STATUS - Wait for verification completion
|
|
1491
|
+
*
|
|
1492
|
+
* Polls the verification status until it reaches a terminal state (completed or failed).
|
|
1493
|
+
* Useful for providing real-time feedback to users during verification.
|
|
1494
|
+
*
|
|
1495
|
+
* @param {string} qHash - Verification ID to poll
|
|
1496
|
+
* @param {Object} [options] - Polling options
|
|
1497
|
+
* @param {number} [options.interval=5000] - Polling interval in ms
|
|
1498
|
+
* @param {number} [options.timeout=120000] - Total timeout in ms
|
|
1499
|
+
* @param {Function} [options.onProgress] - Progress callback function
|
|
1500
|
+
* @returns {Promise<Object>} Final verification status
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* const finalStatus = await client.pollProofStatus(qHash, {
|
|
1504
|
+
* interval: 3000,
|
|
1505
|
+
* timeout: 60000,
|
|
1506
|
+
* onProgress: (status) => {
|
|
1507
|
+
* console.log('Current status:', status.status);
|
|
1508
|
+
* if (status.crosschain) {
|
|
1509
|
+
* console.log(`Cross-chain: ${status.crosschain.finalized}/${status.crosschain.totalChains}`);
|
|
1510
|
+
* }
|
|
1511
|
+
* }
|
|
1512
|
+
* });
|
|
1513
|
+
*/
|
|
1514
|
+
async pollProofStatus(qHash, options = {}) {
|
|
1515
|
+
const {
|
|
1516
|
+
interval = 5e3,
|
|
1517
|
+
timeout = 12e4,
|
|
1518
|
+
onProgress
|
|
1519
|
+
} = options;
|
|
1520
|
+
if (!qHash || typeof qHash !== "string") {
|
|
1521
|
+
throw new ValidationError("qHash is required");
|
|
1522
|
+
}
|
|
1523
|
+
const startTime = Date.now();
|
|
1524
|
+
while (Date.now() - startTime < timeout) {
|
|
1525
|
+
try {
|
|
1526
|
+
const status = await this.getStatus(qHash);
|
|
1527
|
+
if (onProgress && typeof onProgress === "function") {
|
|
1528
|
+
onProgress(status.data || status);
|
|
1529
|
+
}
|
|
1530
|
+
const currentStatus = status.data?.status || status.status;
|
|
1531
|
+
if (this._isTerminalStatus(currentStatus)) {
|
|
1532
|
+
this._log("Verification completed", { status: currentStatus, duration: Date.now() - startTime });
|
|
1533
|
+
return status;
|
|
1534
|
+
}
|
|
1535
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
this._log("Status poll error", error.message);
|
|
1538
|
+
if (error instanceof ValidationError) {
|
|
1539
|
+
throw error;
|
|
1540
|
+
}
|
|
1541
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
throw new NetworkError(`Polling timeout after ${timeout}ms`, "POLLING_TIMEOUT");
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* DETECT CHAIN ID - Get current wallet chain
|
|
1548
|
+
*
|
|
1549
|
+
* @returns {Promise<number>} Current chain ID
|
|
1550
|
+
*/
|
|
1551
|
+
async detectChainId() {
|
|
1552
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
1553
|
+
throw new ConfigurationError("No Web3 wallet detected");
|
|
1554
|
+
}
|
|
1555
|
+
try {
|
|
1556
|
+
const chainId = await window.ethereum.request({ method: "eth_chainId" });
|
|
1557
|
+
return parseInt(chainId, 16);
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
throw new NetworkError(`Failed to detect chain ID: ${error.message}`);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
/** Revoke your own proof (owner-signed) */
|
|
1563
|
+
async revokeOwnProof(qHash, wallet) {
|
|
1564
|
+
if (!qHash || typeof qHash !== "string") {
|
|
1565
|
+
throw new ValidationError("qHash is required");
|
|
1566
|
+
}
|
|
1567
|
+
const address = wallet?.address || await this._getWalletAddress();
|
|
1568
|
+
const signedTimestamp = Date.now();
|
|
1569
|
+
const hubChainId = NEUS_CONSTANTS.HUB_CHAIN_ID;
|
|
1570
|
+
const message = constructVerificationMessage({
|
|
1571
|
+
walletAddress: address,
|
|
1572
|
+
signedTimestamp,
|
|
1573
|
+
data: { action: "revoke_proof", qHash },
|
|
1574
|
+
verifierIds: ["ownership-basic"],
|
|
1575
|
+
chainId: hubChainId
|
|
1576
|
+
});
|
|
1577
|
+
let signature;
|
|
1578
|
+
try {
|
|
1579
|
+
const toHexUtf8 = (s) => {
|
|
1580
|
+
const enc = new TextEncoder();
|
|
1581
|
+
const bytes = enc.encode(s);
|
|
1582
|
+
let hex = "0x";
|
|
1583
|
+
for (let i = 0; i < bytes.length; i++)
|
|
1584
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
1585
|
+
return hex;
|
|
1586
|
+
};
|
|
1587
|
+
const isFarcasterWallet = (() => {
|
|
1588
|
+
if (typeof window === "undefined")
|
|
1589
|
+
return false;
|
|
1590
|
+
try {
|
|
1591
|
+
const w = window;
|
|
1592
|
+
const fc = w.farcaster;
|
|
1593
|
+
if (!fc || !fc.context)
|
|
1594
|
+
return false;
|
|
1595
|
+
const fcProvider = fc.provider || fc.walletProvider || fc.context && fc.context.walletProvider;
|
|
1596
|
+
if (fcProvider === w.ethereum)
|
|
1597
|
+
return true;
|
|
1598
|
+
if (w.mini && w.mini.wallet === w.ethereum && fc && fc.context)
|
|
1599
|
+
return true;
|
|
1600
|
+
if (w.ethereum && fc && fc.context)
|
|
1601
|
+
return true;
|
|
1602
|
+
} catch {
|
|
1603
|
+
}
|
|
1604
|
+
return false;
|
|
1605
|
+
})();
|
|
1606
|
+
if (isFarcasterWallet) {
|
|
1607
|
+
try {
|
|
1608
|
+
const hexMsg = toHexUtf8(message);
|
|
1609
|
+
signature = await window.ethereum.request({ method: "personal_sign", params: [hexMsg, address] });
|
|
1610
|
+
} catch {
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
if (!signature) {
|
|
1614
|
+
signature = await window.ethereum.request({ method: "personal_sign", params: [message, address] });
|
|
1615
|
+
}
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
if (error.code === 4001) {
|
|
1618
|
+
throw new ValidationError("User rejected revocation signature");
|
|
1619
|
+
}
|
|
1620
|
+
throw new ValidationError(`Failed to sign revocation: ${error.message}`);
|
|
1621
|
+
}
|
|
1622
|
+
const res = await fetch(`${this.config.apiUrl}/api/v1/proofs/${qHash}/revoke-self`, {
|
|
1623
|
+
method: "POST",
|
|
1624
|
+
// SECURITY: Do not put proof signatures into Authorization headers.
|
|
1625
|
+
headers: { "Content-Type": "application/json" },
|
|
1626
|
+
body: JSON.stringify({ walletAddress: address, signature, signedTimestamp })
|
|
1627
|
+
});
|
|
1628
|
+
const json = await res.json();
|
|
1629
|
+
if (!json.success) {
|
|
1630
|
+
throw new ApiError(json.error?.message || "Failed to revoke proof", json.error);
|
|
1631
|
+
}
|
|
1632
|
+
return true;
|
|
1633
|
+
}
|
|
1634
|
+
// ============================================================================
|
|
1635
|
+
// GATE & LOOKUP METHODS
|
|
1636
|
+
// ============================================================================
|
|
1637
|
+
/**
|
|
1638
|
+
* GET PROOFS BY WALLET - Fetch proofs for a wallet address
|
|
1639
|
+
*
|
|
1640
|
+
* @param {string} walletAddress - Wallet address (0x...) or DID (did:pkh:...)
|
|
1641
|
+
* @param {Object} [options] - Filter options
|
|
1642
|
+
* @param {number} [options.limit] - Max results (default: 50; higher limits require owner access)
|
|
1643
|
+
* @param {number} [options.offset] - Pagination offset (default: 0)
|
|
1644
|
+
* @returns {Promise<Object>} Proofs result
|
|
1645
|
+
*
|
|
1646
|
+
* @example
|
|
1647
|
+
* const result = await client.getProofsByWallet('0x...', {
|
|
1648
|
+
* limit: 50,
|
|
1649
|
+
* offset: 0
|
|
1650
|
+
* });
|
|
1651
|
+
*/
|
|
1652
|
+
async getProofsByWallet(walletAddress, options = {}) {
|
|
1653
|
+
if (!walletAddress || typeof walletAddress !== "string") {
|
|
1654
|
+
throw new ValidationError("walletAddress is required");
|
|
1655
|
+
}
|
|
1656
|
+
const id = walletAddress.trim();
|
|
1657
|
+
const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
|
|
1658
|
+
const qs = [];
|
|
1659
|
+
if (options.limit)
|
|
1660
|
+
qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
|
|
1661
|
+
if (options.offset)
|
|
1662
|
+
qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
|
|
1663
|
+
const query = qs.length ? `?${qs.join("&")}` : "";
|
|
1664
|
+
const response = await this._makeRequest(
|
|
1665
|
+
"GET",
|
|
1666
|
+
`/api/v1/proofs/byWallet/${encodeURIComponent(pathId)}${query}`
|
|
1667
|
+
);
|
|
1668
|
+
if (!response.success) {
|
|
1669
|
+
throw new ApiError(`Failed to get proofs: ${response.error?.message || "Unknown error"}`, response.error);
|
|
1670
|
+
}
|
|
1671
|
+
const proofs = response.data?.proofs || response.data || response.proofs || [];
|
|
1672
|
+
return {
|
|
1673
|
+
success: true,
|
|
1674
|
+
proofs: Array.isArray(proofs) ? proofs : [],
|
|
1675
|
+
totalCount: response.data?.totalCount ?? proofs.length,
|
|
1676
|
+
hasMore: Boolean(response.data?.hasMore),
|
|
1677
|
+
nextOffset: response.data?.nextOffset ?? null
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Get proofs by wallet (owner access)
|
|
1682
|
+
*
|
|
1683
|
+
* Signs an owner-access intent and requests private proofs via signature headers.
|
|
1684
|
+
*
|
|
1685
|
+
* @param {string} walletAddress - Wallet address (0x...) or DID (did:pkh:...)
|
|
1686
|
+
* @param {Object} [options]
|
|
1687
|
+
* @param {number} [options.limit] - Max results (server enforces caps)
|
|
1688
|
+
* @param {number} [options.offset] - Pagination offset
|
|
1689
|
+
* @param {Object} [wallet] - Optional injected wallet/provider (MetaMask/ethers Wallet)
|
|
1690
|
+
*/
|
|
1691
|
+
async getPrivateProofsByWallet(walletAddress, options = {}, wallet = null) {
|
|
1692
|
+
if (!walletAddress || typeof walletAddress !== "string") {
|
|
1693
|
+
throw new ValidationError("walletAddress is required");
|
|
1694
|
+
}
|
|
1695
|
+
const id = walletAddress.trim();
|
|
1696
|
+
const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
|
|
1697
|
+
if (!wallet) {
|
|
1698
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
1699
|
+
throw new ConfigurationError("No wallet provider available");
|
|
1700
|
+
}
|
|
1701
|
+
wallet = window.ethereum;
|
|
1702
|
+
}
|
|
1703
|
+
let signerWalletAddress, provider;
|
|
1704
|
+
if (wallet.address) {
|
|
1705
|
+
signerWalletAddress = wallet.address;
|
|
1706
|
+
provider = wallet;
|
|
1707
|
+
} else if (wallet.selectedAddress || wallet.request) {
|
|
1708
|
+
provider = wallet;
|
|
1709
|
+
if (wallet.selectedAddress) {
|
|
1710
|
+
signerWalletAddress = wallet.selectedAddress;
|
|
1711
|
+
} else {
|
|
1712
|
+
const accounts = await provider.request({ method: "eth_accounts" });
|
|
1713
|
+
if (!accounts || accounts.length === 0) {
|
|
1714
|
+
throw new ConfigurationError("No wallet accounts available");
|
|
1715
|
+
}
|
|
1716
|
+
signerWalletAddress = accounts[0];
|
|
1717
|
+
}
|
|
1718
|
+
} else {
|
|
1719
|
+
throw new ConfigurationError("Invalid wallet provider");
|
|
1720
|
+
}
|
|
1721
|
+
const signedTimestamp = Date.now();
|
|
1722
|
+
const message = constructVerificationMessage({
|
|
1723
|
+
walletAddress: signerWalletAddress,
|
|
1724
|
+
signedTimestamp,
|
|
1725
|
+
data: { action: "access_private_proofs_by_wallet", walletAddress: signerWalletAddress.toLowerCase() },
|
|
1726
|
+
verifierIds: ["ownership-basic"],
|
|
1727
|
+
chainId: NEUS_CONSTANTS.HUB_CHAIN_ID
|
|
1728
|
+
});
|
|
1729
|
+
let signature;
|
|
1730
|
+
try {
|
|
1731
|
+
if (provider.signMessage) {
|
|
1732
|
+
signature = await provider.signMessage(message);
|
|
1733
|
+
} else {
|
|
1734
|
+
signature = await provider.request({
|
|
1735
|
+
method: "personal_sign",
|
|
1736
|
+
params: [message, signerWalletAddress]
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
if (error.code === 4001) {
|
|
1741
|
+
throw new ValidationError("User rejected signature request");
|
|
1742
|
+
}
|
|
1743
|
+
throw new ValidationError(`Failed to sign message: ${error.message}`);
|
|
1744
|
+
}
|
|
1745
|
+
const qs = [];
|
|
1746
|
+
if (options.limit)
|
|
1747
|
+
qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
|
|
1748
|
+
if (options.offset)
|
|
1749
|
+
qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
|
|
1750
|
+
const query = qs.length ? `?${qs.join("&")}` : "";
|
|
1751
|
+
const response = await this._makeRequest("GET", `/api/v1/proofs/byWallet/${encodeURIComponent(pathId)}${query}`, null, {
|
|
1752
|
+
"x-wallet-address": signerWalletAddress,
|
|
1753
|
+
"x-signature": signature,
|
|
1754
|
+
"x-signed-timestamp": signedTimestamp.toString()
|
|
1755
|
+
});
|
|
1756
|
+
if (!response.success) {
|
|
1757
|
+
throw new ApiError(`Failed to get proofs: ${response.error?.message || "Unauthorized"}`, response.error);
|
|
1758
|
+
}
|
|
1759
|
+
const proofs = response.data?.proofs || response.data || response.proofs || [];
|
|
1760
|
+
return {
|
|
1761
|
+
success: true,
|
|
1762
|
+
proofs: Array.isArray(proofs) ? proofs : [],
|
|
1763
|
+
totalCount: response.data?.totalCount ?? proofs.length,
|
|
1764
|
+
hasMore: Boolean(response.data?.hasMore),
|
|
1765
|
+
nextOffset: response.data?.nextOffset ?? null
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* LOOKUP MODE (API) - Non-persistent server-to-server checks
|
|
1770
|
+
*
|
|
1771
|
+
* Runs `external_lookup` verifiers without minting/storing a proof.
|
|
1772
|
+
* Requires an enterprise API key (server-side only).
|
|
1773
|
+
*
|
|
1774
|
+
* @param {Object} params
|
|
1775
|
+
* @param {string} params.apiKey - Enterprise API key (sk_live_... or sk_test_...)
|
|
1776
|
+
* @param {Array<string>} params.verifierIds - Verifiers to run (external_lookup only)
|
|
1777
|
+
* @param {string} params.targetWalletAddress - Wallet to evaluate
|
|
1778
|
+
* @param {Object} [params.data] - Verifier input data (e.g., contractAddress/tokenId/chainId)
|
|
1779
|
+
* @returns {Promise<Object>} API response ({ success, data })
|
|
1780
|
+
*/
|
|
1781
|
+
async lookup(params = {}) {
|
|
1782
|
+
const apiKey = (params.apiKey || "").toString().trim();
|
|
1783
|
+
if (!apiKey || !(apiKey.startsWith("sk_live_") || apiKey.startsWith("sk_test_"))) {
|
|
1784
|
+
throw new ValidationError("lookup requires apiKey (sk_live_* or sk_test_*)");
|
|
1785
|
+
}
|
|
1786
|
+
const verifierIds = Array.isArray(params.verifierIds) ? params.verifierIds.map((v) => String(v).trim()).filter(Boolean) : [];
|
|
1787
|
+
if (verifierIds.length === 0) {
|
|
1788
|
+
throw new ValidationError("lookup requires verifierIds (non-empty array)");
|
|
1789
|
+
}
|
|
1790
|
+
const targetWalletAddress = (params.targetWalletAddress || "").toString().trim();
|
|
1791
|
+
if (!targetWalletAddress || !/^0x[a-fA-F0-9]{40}$/i.test(targetWalletAddress)) {
|
|
1792
|
+
throw new ValidationError("lookup requires a valid targetWalletAddress (0x...)");
|
|
1793
|
+
}
|
|
1794
|
+
const body = {
|
|
1795
|
+
verifierIds,
|
|
1796
|
+
targetWalletAddress,
|
|
1797
|
+
data: params.data && typeof params.data === "object" ? params.data : {}
|
|
1798
|
+
};
|
|
1799
|
+
const response = await this._makeRequest("POST", "/api/v1/verification/lookup", body, {
|
|
1800
|
+
Authorization: `Bearer ${apiKey}`
|
|
1801
|
+
});
|
|
1802
|
+
if (!response.success) {
|
|
1803
|
+
throw new ApiError(`Lookup failed: ${response.error?.message || "Unknown error"}`, response.error);
|
|
1804
|
+
}
|
|
1805
|
+
return response;
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* GATE CHECK (API) - Minimal eligibility check
|
|
1809
|
+
*
|
|
1810
|
+
* Calls the public gate endpoint and returns a **minimal** yes/no response
|
|
1811
|
+
* against **public + discoverable** proofs only.
|
|
1812
|
+
*
|
|
1813
|
+
* Prefer this over `checkGate()` for server-side integrations that want the
|
|
1814
|
+
* smallest, most stable surface area (and do NOT need full proof payloads).
|
|
1815
|
+
*
|
|
1816
|
+
* @param {Object} params - Gate check query params
|
|
1817
|
+
* @param {string} params.address - Wallet address to check (0x...)
|
|
1818
|
+
* @param {Array<string>|string} [params.verifierIds] - Verifier IDs to match (array or comma-separated)
|
|
1819
|
+
* @param {boolean} [params.requireAll] - Require all verifierIds on a single proof
|
|
1820
|
+
* @param {number} [params.minCount] - Minimum number of matching proofs
|
|
1821
|
+
* @param {number} [params.sinceDays] - Optional time window in days
|
|
1822
|
+
* @param {number} [params.since] - Optional unix timestamp in ms (lower bound)
|
|
1823
|
+
* @param {number} [params.limit] - Max rows to scan (server may clamp)
|
|
1824
|
+
* @param {string} [params.select] - Comma-separated projections (handle,provider,profileUrl,traits.<key>)
|
|
1825
|
+
* @returns {Promise<Object>} API response ({ success, data })
|
|
1826
|
+
*/
|
|
1827
|
+
async gateCheck(params = {}) {
|
|
1828
|
+
const address = (params.address || "").toString();
|
|
1829
|
+
if (!address || !/^0x[a-fA-F0-9]{40}$/i.test(address)) {
|
|
1830
|
+
throw new ValidationError("Valid address is required");
|
|
1831
|
+
}
|
|
1832
|
+
const qs = new URLSearchParams();
|
|
1833
|
+
qs.set("address", address);
|
|
1834
|
+
const setIfPresent = (key, value) => {
|
|
1835
|
+
if (value === void 0 || value === null)
|
|
1836
|
+
return;
|
|
1837
|
+
const str = typeof value === "string" ? value : String(value);
|
|
1838
|
+
if (str.length === 0)
|
|
1839
|
+
return;
|
|
1840
|
+
qs.set(key, str);
|
|
1841
|
+
};
|
|
1842
|
+
const setBoolIfPresent = (key, value) => {
|
|
1843
|
+
if (value === void 0 || value === null)
|
|
1844
|
+
return;
|
|
1845
|
+
qs.set(key, value ? "true" : "false");
|
|
1846
|
+
};
|
|
1847
|
+
const setCsvIfPresent = (key, value) => {
|
|
1848
|
+
if (value === void 0 || value === null)
|
|
1849
|
+
return;
|
|
1850
|
+
if (Array.isArray(value)) {
|
|
1851
|
+
const items = value.map((v) => String(v).trim()).filter(Boolean);
|
|
1852
|
+
if (items.length)
|
|
1853
|
+
qs.set(key, items.join(","));
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
setIfPresent(key, value);
|
|
1857
|
+
};
|
|
1858
|
+
setCsvIfPresent("verifierIds", params.verifierIds);
|
|
1859
|
+
setBoolIfPresent("requireAll", params.requireAll);
|
|
1860
|
+
setIfPresent("minCount", params.minCount);
|
|
1861
|
+
setIfPresent("sinceDays", params.sinceDays);
|
|
1862
|
+
setIfPresent("since", params.since);
|
|
1863
|
+
setIfPresent("limit", params.limit);
|
|
1864
|
+
setCsvIfPresent("select", params.select);
|
|
1865
|
+
setIfPresent("referenceType", params.referenceType);
|
|
1866
|
+
setIfPresent("referenceId", params.referenceId);
|
|
1867
|
+
setIfPresent("tag", params.tag);
|
|
1868
|
+
setCsvIfPresent("tags", params.tags);
|
|
1869
|
+
setIfPresent("contentType", params.contentType);
|
|
1870
|
+
setIfPresent("content", params.content);
|
|
1871
|
+
setIfPresent("contentHash", params.contentHash);
|
|
1872
|
+
setIfPresent("contractAddress", params.contractAddress);
|
|
1873
|
+
setIfPresent("tokenId", params.tokenId);
|
|
1874
|
+
setIfPresent("chainId", params.chainId);
|
|
1875
|
+
setIfPresent("domain", params.domain);
|
|
1876
|
+
setIfPresent("minBalance", params.minBalance);
|
|
1877
|
+
setIfPresent("provider", params.provider);
|
|
1878
|
+
setIfPresent("handle", params.handle);
|
|
1879
|
+
setIfPresent("ownerAddress", params.ownerAddress);
|
|
1880
|
+
setIfPresent("riskLevel", params.riskLevel);
|
|
1881
|
+
setBoolIfPresent("sanctioned", params.sanctioned);
|
|
1882
|
+
setBoolIfPresent("poisoned", params.poisoned);
|
|
1883
|
+
setIfPresent("primaryWalletAddress", params.primaryWalletAddress);
|
|
1884
|
+
setIfPresent("secondaryWalletAddress", params.secondaryWalletAddress);
|
|
1885
|
+
setIfPresent("verificationMethod", params.verificationMethod);
|
|
1886
|
+
setIfPresent("traitPath", params.traitPath);
|
|
1887
|
+
setIfPresent("traitGte", params.traitGte);
|
|
1888
|
+
const response = await this._makeRequest("GET", `/api/v1/proofs/gate/check?${qs.toString()}`);
|
|
1889
|
+
if (!response.success) {
|
|
1890
|
+
throw new ApiError(`Gate check failed: ${response.error?.message || "Unknown error"}`, response.error);
|
|
1891
|
+
}
|
|
1892
|
+
return response;
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* CHECK GATE - Evaluate requirements against existing proofs
|
|
1896
|
+
*
|
|
1897
|
+
* Gate-first verification: checks if wallet has valid proofs satisfying requirements.
|
|
1898
|
+
* Returns which requirements are missing/expired.
|
|
1899
|
+
*
|
|
1900
|
+
* @param {Object} params - Gate check parameters
|
|
1901
|
+
* @param {string} params.walletAddress - Target wallet
|
|
1902
|
+
* @param {Array<Object>} params.requirements - Array of gate requirements
|
|
1903
|
+
* @param {string} params.requirements[].verifierId - Required verifier ID
|
|
1904
|
+
* @param {number} [params.requirements[].maxAgeMs] - Max proof age in ms (TTL)
|
|
1905
|
+
* @param {boolean} [params.requirements[].optional] - If true, not required for gate satisfaction
|
|
1906
|
+
* @param {number} [params.requirements[].minCount] - Minimum proofs needed (default: 1)
|
|
1907
|
+
* @param {Object} [params.requirements[].match] - Verifier data match criteria
|
|
1908
|
+
* Supports nested fields: 'reference.type', 'reference.id', 'content', 'contentHash', 'input.*', 'license.*'
|
|
1909
|
+
* Supports verifier-specific:
|
|
1910
|
+
* - NFT/Token: 'contractAddress', 'tokenId', 'chainId', 'ownerAddress', 'minBalance'
|
|
1911
|
+
* - DNS: 'domain', 'walletAddress'
|
|
1912
|
+
* - Wallet-link: 'primaryWalletAddress', 'secondaryWalletAddress', 'chainId'
|
|
1913
|
+
* - Contract-ownership: 'contractAddress', 'chainId', 'owner', 'verificationMethod'
|
|
1914
|
+
* Note: contentHash matching uses approximation in SDK; for exact SHA-256 matching, use backend API
|
|
1915
|
+
* @param {Array} [params.proofs] - Pre-fetched proofs (skip API call)
|
|
1916
|
+
* @returns {Promise<Object>} Gate result with satisfied, missing, existing
|
|
1917
|
+
*
|
|
1918
|
+
* @example
|
|
1919
|
+
* // Basic gate check
|
|
1920
|
+
* const result = await client.checkGate({
|
|
1921
|
+
* walletAddress: '0x...',
|
|
1922
|
+
* requirements: [
|
|
1923
|
+
* { verifierId: 'nft-ownership', match: { contractAddress: '0x...' } }
|
|
1924
|
+
* ]
|
|
1925
|
+
* });
|
|
1926
|
+
*/
|
|
1927
|
+
async checkGate(params) {
|
|
1928
|
+
const { walletAddress, requirements, proofs: preloadedProofs } = params;
|
|
1929
|
+
if (!walletAddress || !/^0x[a-fA-F0-9]{40}$/i.test(walletAddress)) {
|
|
1930
|
+
throw new ValidationError("Valid walletAddress is required");
|
|
1931
|
+
}
|
|
1932
|
+
if (!Array.isArray(requirements) || requirements.length === 0) {
|
|
1933
|
+
throw new ValidationError("requirements array is required and must not be empty");
|
|
1934
|
+
}
|
|
1935
|
+
let proofs = preloadedProofs;
|
|
1936
|
+
if (!proofs) {
|
|
1937
|
+
const result = await this.getProofsByWallet(walletAddress, { limit: 100 });
|
|
1938
|
+
proofs = result.proofs;
|
|
1939
|
+
}
|
|
1940
|
+
const now = Date.now();
|
|
1941
|
+
const existing = {};
|
|
1942
|
+
const missing = [];
|
|
1943
|
+
for (const req of requirements) {
|
|
1944
|
+
const { verifierId, maxAgeMs, optional = false, minCount = 1, match } = req;
|
|
1945
|
+
const matchingProofs = (proofs || []).filter((proof) => {
|
|
1946
|
+
const verifiedVerifiers = proof.verifiedVerifiers || [];
|
|
1947
|
+
const verifier = verifiedVerifiers.find(
|
|
1948
|
+
(v) => v.verifierId === verifierId && v.verified === true
|
|
1949
|
+
);
|
|
1950
|
+
if (!verifier)
|
|
1951
|
+
return false;
|
|
1952
|
+
if (proof.revokedAt)
|
|
1953
|
+
return false;
|
|
1954
|
+
if (maxAgeMs) {
|
|
1955
|
+
const proofTimestamp = proof.createdAt || proof.signedTimestamp || 0;
|
|
1956
|
+
const proofAge = now - proofTimestamp;
|
|
1957
|
+
if (proofAge > maxAgeMs)
|
|
1958
|
+
return false;
|
|
1959
|
+
}
|
|
1960
|
+
if (match && typeof match === "object") {
|
|
1961
|
+
const data = verifier.data || {};
|
|
1962
|
+
const input = data.input || {};
|
|
1963
|
+
for (const [key, expected] of Object.entries(match)) {
|
|
1964
|
+
let actualValue = null;
|
|
1965
|
+
if (key.includes(".")) {
|
|
1966
|
+
const parts = key.split(".");
|
|
1967
|
+
let current = data;
|
|
1968
|
+
if (parts[0] === "input" && input) {
|
|
1969
|
+
current = input;
|
|
1970
|
+
parts.shift();
|
|
1971
|
+
}
|
|
1972
|
+
for (const part of parts) {
|
|
1973
|
+
if (current && typeof current === "object" && part in current) {
|
|
1974
|
+
current = current[part];
|
|
1975
|
+
} else {
|
|
1976
|
+
current = void 0;
|
|
1977
|
+
break;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
actualValue = current;
|
|
1981
|
+
} else {
|
|
1982
|
+
actualValue = input[key] || data[key];
|
|
1983
|
+
}
|
|
1984
|
+
if (key === "content" && actualValue === void 0) {
|
|
1985
|
+
actualValue = data.reference?.id || data.content;
|
|
1986
|
+
}
|
|
1987
|
+
if (actualValue === void 0) {
|
|
1988
|
+
if (key === "contractAddress") {
|
|
1989
|
+
actualValue = input.contractAddress || data.contractAddress;
|
|
1990
|
+
} else if (key === "tokenId") {
|
|
1991
|
+
actualValue = input.tokenId || data.tokenId;
|
|
1992
|
+
} else if (key === "chainId") {
|
|
1993
|
+
actualValue = input.chainId || data.chainId;
|
|
1994
|
+
} else if (key === "ownerAddress") {
|
|
1995
|
+
actualValue = input.ownerAddress || data.owner || data.walletAddress;
|
|
1996
|
+
} else if (key === "minBalance") {
|
|
1997
|
+
actualValue = input.minBalance || data.onChainData?.requiredMinBalance || data.minBalance;
|
|
1998
|
+
} else if (key === "primaryWalletAddress") {
|
|
1999
|
+
actualValue = data.primaryWalletAddress;
|
|
2000
|
+
} else if (key === "secondaryWalletAddress") {
|
|
2001
|
+
actualValue = data.secondaryWalletAddress;
|
|
2002
|
+
} else if (key === "verificationMethod") {
|
|
2003
|
+
actualValue = data.verificationMethod;
|
|
2004
|
+
} else if (key === "domain") {
|
|
2005
|
+
actualValue = data.domain;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
if (key === "contentHash" && actualValue === void 0 && data.content) {
|
|
2009
|
+
try {
|
|
2010
|
+
let hash = 0;
|
|
2011
|
+
const str = String(data.content);
|
|
2012
|
+
for (let i = 0; i < str.length; i++) {
|
|
2013
|
+
const char = str.charCodeAt(i);
|
|
2014
|
+
hash = (hash << 5) - hash + char;
|
|
2015
|
+
hash = hash & hash;
|
|
2016
|
+
}
|
|
2017
|
+
actualValue = "0x" + Math.abs(hash).toString(16).padStart(64, "0").substring(0, 66);
|
|
2018
|
+
} catch {
|
|
2019
|
+
actualValue = String(data.content);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
let normalizedActual = actualValue;
|
|
2023
|
+
let normalizedExpected = expected;
|
|
2024
|
+
if (actualValue === void 0 || actualValue === null) {
|
|
2025
|
+
return false;
|
|
2026
|
+
}
|
|
2027
|
+
if (key === "chainId" || key === "tokenId" && (typeof actualValue === "number" || !isNaN(Number(actualValue)))) {
|
|
2028
|
+
normalizedActual = Number(actualValue);
|
|
2029
|
+
normalizedExpected = Number(expected);
|
|
2030
|
+
if (isNaN(normalizedActual) || isNaN(normalizedExpected))
|
|
2031
|
+
return false;
|
|
2032
|
+
} else if (typeof actualValue === "string" && (actualValue.startsWith("0x") || actualValue.length > 20)) {
|
|
2033
|
+
normalizedActual = actualValue.toLowerCase();
|
|
2034
|
+
normalizedExpected = typeof expected === "string" ? String(expected).toLowerCase() : expected;
|
|
2035
|
+
} else {
|
|
2036
|
+
normalizedActual = actualValue;
|
|
2037
|
+
normalizedExpected = expected;
|
|
2038
|
+
}
|
|
2039
|
+
if (normalizedActual !== normalizedExpected) {
|
|
2040
|
+
return false;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
return true;
|
|
2045
|
+
});
|
|
2046
|
+
if (matchingProofs.length >= minCount) {
|
|
2047
|
+
const sorted = matchingProofs.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
2048
|
+
existing[verifierId] = sorted[0];
|
|
2049
|
+
} else if (!optional) {
|
|
2050
|
+
missing.push(req);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
return {
|
|
2054
|
+
satisfied: missing.length === 0,
|
|
2055
|
+
missing,
|
|
2056
|
+
existing,
|
|
2057
|
+
allProofs: proofs
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
// ============================================================================
|
|
2061
|
+
// PRIVATE UTILITY METHODS
|
|
2062
|
+
// ============================================================================
|
|
2063
|
+
/**
|
|
2064
|
+
* Get connected wallet address
|
|
2065
|
+
* @private
|
|
2066
|
+
*/
|
|
2067
|
+
async _getWalletAddress() {
|
|
2068
|
+
if (typeof window === "undefined" || !window.ethereum) {
|
|
2069
|
+
throw new ConfigurationError("No Web3 wallet detected");
|
|
2070
|
+
}
|
|
2071
|
+
const accounts = await window.ethereum.request({ method: "eth_accounts" });
|
|
2072
|
+
if (!accounts || accounts.length === 0) {
|
|
2073
|
+
throw new ConfigurationError("No wallet accounts available");
|
|
2074
|
+
}
|
|
2075
|
+
return accounts[0];
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Make HTTP request to API
|
|
2079
|
+
* @private
|
|
2080
|
+
*/
|
|
2081
|
+
async _makeRequest(method, endpoint, data = null, headersOverride = null) {
|
|
2082
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
2083
|
+
const controller = new AbortController();
|
|
2084
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
2085
|
+
const options = {
|
|
2086
|
+
method,
|
|
2087
|
+
headers: { ...this.defaultHeaders, ...headersOverride || {} },
|
|
2088
|
+
signal: controller.signal
|
|
2089
|
+
};
|
|
2090
|
+
if (data && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
2091
|
+
options.body = JSON.stringify(data);
|
|
2092
|
+
}
|
|
2093
|
+
this._log(`${method} ${endpoint}`, data ? { requestBodyKeys: Object.keys(data) } : {});
|
|
2094
|
+
try {
|
|
2095
|
+
const response = await fetch(url, options);
|
|
2096
|
+
clearTimeout(timeoutId);
|
|
2097
|
+
let responseData;
|
|
2098
|
+
try {
|
|
2099
|
+
responseData = await response.json();
|
|
2100
|
+
} catch {
|
|
2101
|
+
responseData = { error: { message: "Invalid JSON response" } };
|
|
2102
|
+
}
|
|
2103
|
+
if (!response.ok) {
|
|
2104
|
+
throw ApiError.fromResponse(response, responseData);
|
|
2105
|
+
}
|
|
2106
|
+
return responseData;
|
|
2107
|
+
} catch (error) {
|
|
2108
|
+
clearTimeout(timeoutId);
|
|
2109
|
+
if (error.name === "AbortError") {
|
|
2110
|
+
throw new NetworkError(`Request timeout after ${this.config.timeout}ms`);
|
|
2111
|
+
}
|
|
2112
|
+
if (error instanceof ApiError) {
|
|
2113
|
+
throw error;
|
|
2114
|
+
}
|
|
2115
|
+
throw new NetworkError(`Network error: ${error.message}`);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Format API response for consistent structure
|
|
2120
|
+
* @private
|
|
2121
|
+
*/
|
|
2122
|
+
_formatResponse(response) {
|
|
2123
|
+
const qHash = response?.data?.qHash || response?.qHash || response?.data?.resource?.qHash || response?.data?.id;
|
|
2124
|
+
const status = response?.data?.status || response?.status || response?.data?.resource?.status || (response?.success ? "completed" : "unknown");
|
|
2125
|
+
return {
|
|
2126
|
+
success: response.success,
|
|
2127
|
+
qHash,
|
|
2128
|
+
status,
|
|
2129
|
+
data: response.data,
|
|
2130
|
+
message: response.message,
|
|
2131
|
+
timestamp: Date.now(),
|
|
2132
|
+
statusUrl: qHash ? `${this.baseUrl}/api/v1/verification/status/${qHash}` : null
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Check if status is terminal (completed or failed)
|
|
2137
|
+
* @private
|
|
2138
|
+
*/
|
|
2139
|
+
_isTerminalStatus(status) {
|
|
2140
|
+
const terminalStates = [
|
|
2141
|
+
"verified",
|
|
2142
|
+
"verified_crosschain_propagated",
|
|
2143
|
+
"completed_all_successful",
|
|
2144
|
+
"failed",
|
|
2145
|
+
"error",
|
|
2146
|
+
"rejected",
|
|
2147
|
+
"cancelled"
|
|
2148
|
+
];
|
|
2149
|
+
return typeof status === "string" && terminalStates.some((state) => status.includes(state));
|
|
2150
|
+
}
|
|
2151
|
+
/** SDK logging (opt-in via config.enableLogging) */
|
|
2152
|
+
_log(message, data = {}) {
|
|
2153
|
+
if (this.config.enableLogging) {
|
|
2154
|
+
try {
|
|
2155
|
+
const prefix = "[neus/sdk]";
|
|
2156
|
+
if (data && Object.keys(data).length > 0) {
|
|
2157
|
+
console.log(prefix, message, data);
|
|
2158
|
+
} else {
|
|
2159
|
+
console.log(prefix, message);
|
|
2160
|
+
}
|
|
2161
|
+
} catch {
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
|
|
2169
|
+
// index.js
|
|
2170
|
+
var sdk_exports = {};
|
|
2171
|
+
__export(sdk_exports, {
|
|
2172
|
+
ApiError: () => ApiError,
|
|
2173
|
+
AuthenticationError: () => AuthenticationError,
|
|
2174
|
+
ConfigurationError: () => ConfigurationError,
|
|
2175
|
+
DAY: () => DAY,
|
|
2176
|
+
GATE_CONTRACT_ADMIN: () => GATE_CONTRACT_ADMIN,
|
|
2177
|
+
GATE_DOMAIN_OWNER: () => GATE_DOMAIN_OWNER,
|
|
2178
|
+
GATE_LINKED_WALLETS: () => GATE_LINKED_WALLETS,
|
|
2179
|
+
GATE_NFT_HOLDER: () => GATE_NFT_HOLDER,
|
|
2180
|
+
GATE_TOKEN_HOLDER: () => GATE_TOKEN_HOLDER,
|
|
2181
|
+
HOUR: () => HOUR,
|
|
2182
|
+
MONTH: () => MONTH,
|
|
2183
|
+
NEUS_CONSTANTS: () => NEUS_CONSTANTS,
|
|
2184
|
+
NetworkError: () => NetworkError,
|
|
2185
|
+
NeusClient: () => NeusClient,
|
|
2186
|
+
SDKError: () => SDKError,
|
|
2187
|
+
StatusPoller: () => StatusPoller,
|
|
2188
|
+
ValidationError: () => ValidationError,
|
|
2189
|
+
VerificationError: () => VerificationError,
|
|
2190
|
+
WEEK: () => WEEK,
|
|
2191
|
+
YEAR: () => YEAR,
|
|
2192
|
+
buildVerificationRequest: () => buildVerificationRequest,
|
|
2193
|
+
combineGates: () => combineGates,
|
|
2194
|
+
computeContentHash: () => computeContentHash,
|
|
2195
|
+
constructVerificationMessage: () => constructVerificationMessage,
|
|
2196
|
+
createGate: () => createGate,
|
|
2197
|
+
createVerificationData: () => createVerificationData,
|
|
2198
|
+
default: () => sdk_default,
|
|
2199
|
+
delay: () => delay,
|
|
2200
|
+
deriveDid: () => deriveDid,
|
|
2201
|
+
formatTimestamp: () => formatTimestamp,
|
|
2202
|
+
formatVerificationStatus: () => formatVerificationStatus,
|
|
2203
|
+
isFailureStatus: () => isFailureStatus,
|
|
2204
|
+
isSuccessStatus: () => isSuccessStatus,
|
|
2205
|
+
isSupportedChain: () => isSupportedChain,
|
|
2206
|
+
isTerminalStatus: () => isTerminalStatus,
|
|
2207
|
+
normalizeAddress: () => normalizeAddress,
|
|
2208
|
+
validateQHash: () => validateQHash,
|
|
2209
|
+
validateSignatureComponents: () => validateSignatureComponents,
|
|
2210
|
+
validateTimestamp: () => validateTimestamp,
|
|
2211
|
+
validateVerifierPayload: () => validateVerifierPayload,
|
|
2212
|
+
validateWalletAddress: () => validateWalletAddress,
|
|
2213
|
+
withRetry: () => withRetry
|
|
2214
|
+
});
|
|
2215
|
+
module.exports = __toCommonJS(sdk_exports);
|
|
2216
|
+
init_client();
|
|
2217
|
+
init_utils();
|
|
2218
|
+
|
|
2219
|
+
// gates.js
|
|
2220
|
+
var HOUR = 60 * 60 * 1e3;
|
|
2221
|
+
var DAY = 24 * HOUR;
|
|
2222
|
+
var WEEK = 7 * DAY;
|
|
2223
|
+
var MONTH = 30 * DAY;
|
|
2224
|
+
var YEAR = 365 * DAY;
|
|
2225
|
+
var GATE_NFT_HOLDER = [
|
|
2226
|
+
{ verifierId: "nft-ownership" }
|
|
2227
|
+
];
|
|
2228
|
+
var GATE_TOKEN_HOLDER = [
|
|
2229
|
+
{ verifierId: "token-holding" }
|
|
2230
|
+
];
|
|
2231
|
+
var GATE_CONTRACT_ADMIN = [
|
|
2232
|
+
{ verifierId: "contract-ownership", maxAgeMs: HOUR }
|
|
2233
|
+
];
|
|
2234
|
+
var GATE_DOMAIN_OWNER = [
|
|
2235
|
+
{ verifierId: "ownership-dns-txt" }
|
|
2236
|
+
];
|
|
2237
|
+
var GATE_LINKED_WALLETS = [
|
|
2238
|
+
{ verifierId: "wallet-link" }
|
|
2239
|
+
];
|
|
2240
|
+
var GATE_AGENT_DELEGATION = [
|
|
2241
|
+
{ verifierId: "agent-delegation", maxAgeMs: 7 * DAY }
|
|
2242
|
+
];
|
|
2243
|
+
function createGate(requirements) {
|
|
2244
|
+
return requirements.map((req) => {
|
|
2245
|
+
if (typeof req === "string") {
|
|
2246
|
+
return { verifierId: req };
|
|
2247
|
+
}
|
|
2248
|
+
return req;
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
function combineGates(...gates) {
|
|
2252
|
+
const combined = [];
|
|
2253
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2254
|
+
for (const gate of gates) {
|
|
2255
|
+
for (const req of gate) {
|
|
2256
|
+
const key = req.verifierId + JSON.stringify(req.match || {});
|
|
2257
|
+
if (!seen.has(key)) {
|
|
2258
|
+
seen.add(key);
|
|
2259
|
+
combined.push(req);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
return combined;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// index.js
|
|
2267
|
+
init_errors();
|
|
2268
|
+
var sdk_default = {
|
|
2269
|
+
NeusClient: () => Promise.resolve().then(() => (init_client(), client_exports)).then((m) => m.NeusClient),
|
|
2270
|
+
toString: () => "[neus/sdk]"
|
|
2271
|
+
};
|
|
2272
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2273
|
+
0 && (module.exports = {
|
|
2274
|
+
ApiError,
|
|
2275
|
+
AuthenticationError,
|
|
2276
|
+
ConfigurationError,
|
|
2277
|
+
DAY,
|
|
2278
|
+
GATE_CONTRACT_ADMIN,
|
|
2279
|
+
GATE_DOMAIN_OWNER,
|
|
2280
|
+
GATE_LINKED_WALLETS,
|
|
2281
|
+
GATE_NFT_HOLDER,
|
|
2282
|
+
GATE_TOKEN_HOLDER,
|
|
2283
|
+
HOUR,
|
|
2284
|
+
MONTH,
|
|
2285
|
+
NEUS_CONSTANTS,
|
|
2286
|
+
NetworkError,
|
|
2287
|
+
NeusClient,
|
|
2288
|
+
SDKError,
|
|
2289
|
+
StatusPoller,
|
|
2290
|
+
ValidationError,
|
|
2291
|
+
VerificationError,
|
|
2292
|
+
WEEK,
|
|
2293
|
+
YEAR,
|
|
2294
|
+
buildVerificationRequest,
|
|
2295
|
+
combineGates,
|
|
2296
|
+
computeContentHash,
|
|
2297
|
+
constructVerificationMessage,
|
|
2298
|
+
createGate,
|
|
2299
|
+
createVerificationData,
|
|
2300
|
+
delay,
|
|
2301
|
+
deriveDid,
|
|
2302
|
+
formatTimestamp,
|
|
2303
|
+
formatVerificationStatus,
|
|
2304
|
+
isFailureStatus,
|
|
2305
|
+
isSuccessStatus,
|
|
2306
|
+
isSupportedChain,
|
|
2307
|
+
isTerminalStatus,
|
|
2308
|
+
normalizeAddress,
|
|
2309
|
+
validateQHash,
|
|
2310
|
+
validateSignatureComponents,
|
|
2311
|
+
validateTimestamp,
|
|
2312
|
+
validateVerifierPayload,
|
|
2313
|
+
validateWalletAddress,
|
|
2314
|
+
withRetry
|
|
2315
|
+
});
|