@neus/sdk 1.1.5 → 1.1.7
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/cjs/client.cjs +96 -17
- package/cjs/index.cjs +96 -17
- package/cli/neus.mjs +31 -6
- package/client.js +2121 -2037
- package/package.json +150 -147
- package/types.d.ts +146 -3
package/client.js
CHANGED
|
@@ -1,2037 +1,2121 @@
|
|
|
1
|
-
import { ApiError, ValidationError, NetworkError, ConfigurationError } from './errors.js';
|
|
2
|
-
import { fetchSponsorGrant } from './sponsor.js';
|
|
3
|
-
import {
|
|
4
|
-
PORTABLE_PROOF_SIGNER_HEADER,
|
|
5
|
-
constructVerificationMessage,
|
|
6
|
-
validateWalletAddress,
|
|
7
|
-
validateUniversalAddress,
|
|
8
|
-
signMessage,
|
|
9
|
-
NEUS_CONSTANTS
|
|
10
|
-
} from './utils.js';
|
|
11
|
-
|
|
12
|
-
const FALLBACK_PUBLIC_VERIFIER_CATALOG = {
|
|
13
|
-
'ownership-basic': { supportsDirectApi: true },
|
|
14
|
-
'ownership-social': { supportsDirectApi: false },
|
|
15
|
-
'ownership-pseudonym': { supportsDirectApi: true },
|
|
16
|
-
'ownership-dns-txt': { supportsDirectApi: true },
|
|
17
|
-
'ownership-org-oauth': { supportsDirectApi: false },
|
|
18
|
-
'contract-ownership': { supportsDirectApi: true },
|
|
19
|
-
'proof-of-human': { supportsDirectApi: false },
|
|
20
|
-
'nft-ownership': { supportsDirectApi: true },
|
|
21
|
-
'token-holding': { supportsDirectApi: true },
|
|
22
|
-
'wallet-risk': { supportsDirectApi: true },
|
|
23
|
-
'wallet-link': { supportsDirectApi: true },
|
|
24
|
-
'ai-content-moderation': { supportsDirectApi: true },
|
|
25
|
-
'agent-identity': { supportsDirectApi: true },
|
|
26
|
-
'agent-delegation': { supportsDirectApi: true }
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
30
|
-
const WALLET_LINK_RELATIONSHIP_TYPES = new Set(['primary', 'personal', 'org', 'affiliate', 'agent', 'linked']);
|
|
31
|
-
|
|
32
|
-
function normalizeWalletLinkRelationshipType(value) {
|
|
33
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
34
|
-
return WALLET_LINK_RELATIONSHIP_TYPES.has(normalized) ? normalized : 'linked';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @param {unknown} raw
|
|
39
|
-
* @returns {string | null} Non-empty trimmed string, or null if the provider value is not a valid string identity.
|
|
40
|
-
*/
|
|
41
|
-
function normalizeBrowserSignerString(raw) {
|
|
42
|
-
if (raw === null || raw === undefined) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
if (typeof raw === 'string') {
|
|
46
|
-
const t = raw.trim();
|
|
47
|
-
return t.length > 0 ? t : null;
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Treats SDK placeholder signatures as absent so the protocol can use session or app-link delegation. */
|
|
53
|
-
function isPlaceholderNeusSignature(signature) {
|
|
54
|
-
const s = typeof signature === 'string' ? signature.trim() : '';
|
|
55
|
-
if (!s) return true;
|
|
56
|
-
return /^0x0+$/i.test(s);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const validateVerifierData = (verifierId, data) => {
|
|
60
|
-
if (!data || typeof data !== 'object') {
|
|
61
|
-
return { valid: false, error: 'Data object is required' };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
switch (verifierId) {
|
|
65
|
-
case 'ownership-basic':
|
|
66
|
-
if (!data.owner || !validateUniversalAddress(data.owner, typeof data.chain === 'string' ? data.chain : undefined)) {
|
|
67
|
-
return { valid: false, error: 'owner (universal wallet address) is required' };
|
|
68
|
-
}
|
|
69
|
-
if (data.content !== undefined && data.content !== null) {
|
|
70
|
-
if (typeof data.content !== 'string') {
|
|
71
|
-
return { valid: false, error: 'content must be a string when provided' };
|
|
72
|
-
}
|
|
73
|
-
if (data.content.length > 50000) {
|
|
74
|
-
return { valid: false, error: 'content exceeds 50KB inline limit' };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (data.contentHash !== undefined && data.contentHash !== null) {
|
|
78
|
-
if (typeof data.contentHash !== 'string' || !/^0x[a-fA-F0-9]{64}$/.test(data.contentHash)) {
|
|
79
|
-
return { valid: false, error: 'contentHash must be a 32-byte hex string (0x + 64 hex chars) when provided' };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (data.contentType !== undefined && data.contentType !== null) {
|
|
83
|
-
if (typeof data.contentType !== 'string' || data.contentType.length > 100) {
|
|
84
|
-
return { valid: false, error: 'contentType must be a string (max 100 chars) when provided' };
|
|
85
|
-
}
|
|
86
|
-
const base = String(data.contentType).split(';')[0].trim().toLowerCase();
|
|
87
|
-
if (!base || base.includes(' ') || !base.includes('/')) {
|
|
88
|
-
return { valid: false, error: 'contentType must be a valid MIME type when provided' };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (data.provenance !== undefined && data.provenance !== null) {
|
|
92
|
-
if (!data.provenance || typeof data.provenance !== 'object' || Array.isArray(data.provenance)) {
|
|
93
|
-
return { valid: false, error: 'provenance must be an object when provided' };
|
|
94
|
-
}
|
|
95
|
-
const dk = data.provenance.declaredKind;
|
|
96
|
-
if (dk !== undefined && dk !== null) {
|
|
97
|
-
const allowed = ['human', 'ai', 'mixed', 'unknown'];
|
|
98
|
-
if (typeof dk !== 'string' || !allowed.includes(dk)) {
|
|
99
|
-
return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(', ')}` };
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
const ai = data.provenance.aiContext;
|
|
103
|
-
if (ai !== undefined && ai !== null) {
|
|
104
|
-
if (typeof ai !== 'object' || Array.isArray(ai)) {
|
|
105
|
-
return { valid: false, error: 'provenance.aiContext must be an object when provided' };
|
|
106
|
-
}
|
|
107
|
-
if (ai.generatorType !== undefined && ai.generatorType !== null) {
|
|
108
|
-
const allowed = ['local', 'saas', 'agent'];
|
|
109
|
-
if (typeof ai.generatorType !== 'string' || !allowed.includes(ai.generatorType)) {
|
|
110
|
-
return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(', ')}` };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (ai.provider !== undefined && ai.provider !== null) {
|
|
114
|
-
if (typeof ai.provider !== 'string' || ai.provider.length > 64) {
|
|
115
|
-
return { valid: false, error: 'provenance.aiContext.provider must be a string (max 64 chars) when provided' };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (ai.model !== undefined && ai.model !== null) {
|
|
119
|
-
if (typeof ai.model !== 'string' || ai.model.length > 128) {
|
|
120
|
-
return { valid: false, error: 'provenance.aiContext.model must be a string (max 128 chars) when provided' };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (ai.runId !== undefined && ai.runId !== null) {
|
|
124
|
-
if (typeof ai.runId !== 'string' || ai.runId.length > 128) {
|
|
125
|
-
return { valid: false, error: 'provenance.aiContext.runId must be a string (max 128 chars) when provided' };
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (data.reference !== undefined) {
|
|
131
|
-
if (!data.reference || typeof data.reference !== 'object') {
|
|
132
|
-
return { valid: false, error: 'reference must be an object when provided' };
|
|
133
|
-
}
|
|
134
|
-
if (!data.reference.type || typeof data.reference.type !== 'string') {
|
|
135
|
-
return { valid: false, error: 'reference.type is required when reference is provided' };
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (!data.content && !data.contentHash) {
|
|
139
|
-
if (!data.reference || typeof data.reference !== 'object') {
|
|
140
|
-
return { valid: false, error: 'reference is required when neither content nor contentHash is provided' };
|
|
141
|
-
}
|
|
142
|
-
if (!data.reference.id || typeof data.reference.id !== 'string') {
|
|
143
|
-
return { valid: false, error: 'reference.id is required when neither content nor contentHash is provided' };
|
|
144
|
-
}
|
|
145
|
-
if (!data.reference.type || typeof data.reference.type !== 'string') {
|
|
146
|
-
return { valid: false, error: 'reference.type is required when neither content nor contentHash is provided' };
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
break;
|
|
150
|
-
case 'nft-ownership':
|
|
151
|
-
if (
|
|
152
|
-
!data.contractAddress ||
|
|
153
|
-
data.tokenId === null ||
|
|
154
|
-
data.tokenId === undefined ||
|
|
155
|
-
typeof data.chainId !== 'number'
|
|
156
|
-
) {
|
|
157
|
-
return { valid: false, error: 'contractAddress, tokenId, and chainId are required' };
|
|
158
|
-
}
|
|
159
|
-
if (data.tokenType !== undefined && data.tokenType !== null) {
|
|
160
|
-
const tt = String(data.tokenType).toLowerCase();
|
|
161
|
-
if (tt !== 'erc721' && tt !== 'erc1155') {
|
|
162
|
-
return { valid: false, error: 'tokenType must be one of: erc721, erc1155' };
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
|
|
166
|
-
return { valid: false, error: 'blockNumber must be an integer when provided' };
|
|
167
|
-
}
|
|
168
|
-
if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
|
|
169
|
-
return { valid: false, error: 'Invalid ownerAddress' };
|
|
170
|
-
}
|
|
171
|
-
if (!validateWalletAddress(data.contractAddress)) {
|
|
172
|
-
return { valid: false, error: 'Invalid contractAddress' };
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
case 'token-holding':
|
|
176
|
-
if (
|
|
177
|
-
!data.contractAddress ||
|
|
178
|
-
data.minBalance === null ||
|
|
179
|
-
data.minBalance === undefined ||
|
|
180
|
-
typeof data.chainId !== 'number'
|
|
181
|
-
) {
|
|
182
|
-
return { valid: false, error: 'contractAddress, minBalance, and chainId are required' };
|
|
183
|
-
}
|
|
184
|
-
if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
|
|
185
|
-
return { valid: false, error: 'blockNumber must be an integer when provided' };
|
|
186
|
-
}
|
|
187
|
-
if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
|
|
188
|
-
return { valid: false, error: 'Invalid ownerAddress' };
|
|
189
|
-
}
|
|
190
|
-
if (!validateWalletAddress(data.contractAddress)) {
|
|
191
|
-
return { valid: false, error: 'Invalid contractAddress' };
|
|
192
|
-
}
|
|
193
|
-
break;
|
|
194
|
-
case 'ownership-dns-txt':
|
|
195
|
-
if (!data.domain || typeof data.domain !== 'string') {
|
|
196
|
-
return { valid: false, error: 'domain is required' };
|
|
197
|
-
}
|
|
198
|
-
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
199
|
-
return { valid: false, error: 'Invalid walletAddress' };
|
|
200
|
-
}
|
|
201
|
-
break;
|
|
202
|
-
case 'wallet-link':
|
|
203
|
-
if (!data.primaryWalletAddress || !validateUniversalAddress(data.primaryWalletAddress, data.chain)) {
|
|
204
|
-
return { valid: false, error: 'primaryWalletAddress is required' };
|
|
205
|
-
}
|
|
206
|
-
if (!data.secondaryWalletAddress || !validateUniversalAddress(data.secondaryWalletAddress, data.chain)) {
|
|
207
|
-
return { valid: false, error: 'secondaryWalletAddress is required' };
|
|
208
|
-
}
|
|
209
|
-
if (!data.signature || typeof data.signature !== 'string') {
|
|
210
|
-
return { valid: false, error: 'signature is required (signed by secondary wallet)' };
|
|
211
|
-
}
|
|
212
|
-
if (typeof data.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
|
|
213
|
-
return { valid: false, error: 'chain is required (namespace:reference)' };
|
|
214
|
-
}
|
|
215
|
-
if (typeof data.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
|
|
216
|
-
return { valid: false, error: 'signatureMethod is required' };
|
|
217
|
-
}
|
|
218
|
-
if (typeof data.signedTimestamp !== 'number') {
|
|
219
|
-
return { valid: false, error: 'signedTimestamp is required' };
|
|
220
|
-
}
|
|
221
|
-
break;
|
|
222
|
-
case 'contract-ownership':
|
|
223
|
-
if (!data.contractAddress || !validateWalletAddress(data.contractAddress)) {
|
|
224
|
-
return { valid: false, error: 'contractAddress is required' };
|
|
225
|
-
}
|
|
226
|
-
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
227
|
-
return { valid: false, error: 'Invalid walletAddress' };
|
|
228
|
-
}
|
|
229
|
-
if (typeof data.chainId !== 'number') {
|
|
230
|
-
return { valid: false, error: 'chainId is required' };
|
|
231
|
-
}
|
|
232
|
-
break;
|
|
233
|
-
case 'agent-identity':
|
|
234
|
-
if (!data.agentId || typeof data.agentId !== 'string' || data.agentId.length < 1 || data.agentId.length > 128) {
|
|
235
|
-
return { valid: false, error: 'agentId is required (1-128 chars)' };
|
|
236
|
-
}
|
|
237
|
-
if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
|
|
238
|
-
return { valid: false, error: 'agentWallet is required' };
|
|
239
|
-
}
|
|
240
|
-
if (data.agentType && !['ai', 'bot', 'service', 'automation', 'agent'].includes(data.agentType)) {
|
|
241
|
-
return { valid: false, error: 'agentType must be one of: ai, bot, service, automation, agent' };
|
|
242
|
-
}
|
|
243
|
-
if (data.defaultRuntime && typeof data.defaultRuntime === 'object') {
|
|
244
|
-
if (data.defaultRuntime.provider && typeof data.defaultRuntime.provider === 'string' && data.defaultRuntime.provider.length > 64) {
|
|
245
|
-
return { valid: false, error: 'defaultRuntime.provider must be 64 chars or less' };
|
|
246
|
-
}
|
|
247
|
-
if (data.defaultRuntime.model && typeof data.defaultRuntime.model === 'string' && data.defaultRuntime.model.length > 128) {
|
|
248
|
-
return { valid: false, error: 'defaultRuntime.model must be 128 chars or less' };
|
|
249
|
-
}
|
|
250
|
-
if (data.defaultRuntime.mode && typeof data.defaultRuntime.mode === 'string' && data.defaultRuntime.mode.length > 64) {
|
|
251
|
-
return { valid: false, error: 'defaultRuntime.mode must be 64 chars or less' };
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
break;
|
|
255
|
-
case 'agent-delegation':
|
|
256
|
-
if (!data.controllerWallet || !validateWalletAddress(data.controllerWallet)) {
|
|
257
|
-
return { valid: false, error: 'controllerWallet is required' };
|
|
258
|
-
}
|
|
259
|
-
if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
|
|
260
|
-
return { valid: false, error: 'agentWallet is required' };
|
|
261
|
-
}
|
|
262
|
-
if (data.scope && (typeof data.scope !== 'string' || data.scope.length > 128)) {
|
|
263
|
-
return { valid: false, error: 'scope must be a string (max 128 chars)' };
|
|
264
|
-
}
|
|
265
|
-
if (data.expiresAt && (typeof data.expiresAt !== 'number' || data.expiresAt < Date.now())) {
|
|
266
|
-
return { valid: false, error: 'expiresAt must be a future timestamp' };
|
|
267
|
-
}
|
|
268
|
-
if (data.model && typeof data.model === 'string' && data.model.length > 128) {
|
|
269
|
-
return { valid: false, error: 'model must be 128 chars or less' };
|
|
270
|
-
}
|
|
271
|
-
if (data.provider && typeof data.provider === 'string' && data.provider.length > 64) {
|
|
272
|
-
return { valid: false, error: 'provider must be 64 chars or less' };
|
|
273
|
-
}
|
|
274
|
-
if (data.allowedActions && Array.isArray(data.allowedActions) && data.allowedActions.length > 32) {
|
|
275
|
-
return { valid: false, error: 'allowedActions must have 32 items or less' };
|
|
276
|
-
}
|
|
277
|
-
if (data.deniedActions && Array.isArray(data.deniedActions) && data.deniedActions.length > 32) {
|
|
278
|
-
return { valid: false, error: 'deniedActions must have 32 items or less' };
|
|
279
|
-
}
|
|
280
|
-
break;
|
|
281
|
-
case 'ai-content-moderation':
|
|
282
|
-
if (!data.content || typeof data.content !== 'string') {
|
|
283
|
-
return { valid: false, error: 'content is required' };
|
|
284
|
-
}
|
|
285
|
-
if (!data.contentType || typeof data.contentType !== 'string') {
|
|
286
|
-
return { valid: false, error: 'contentType (MIME type) is required' };
|
|
287
|
-
}
|
|
288
|
-
{
|
|
289
|
-
const contentType = String(data.contentType).split(';')[0].trim().toLowerCase();
|
|
290
|
-
const validTypes = [
|
|
291
|
-
'image/jpeg',
|
|
292
|
-
'image/png',
|
|
293
|
-
'image/webp',
|
|
294
|
-
'image/gif',
|
|
295
|
-
'text/plain',
|
|
296
|
-
'text/markdown',
|
|
297
|
-
'text/x-markdown',
|
|
298
|
-
'application/json',
|
|
299
|
-
'application/xml'
|
|
300
|
-
];
|
|
301
|
-
if (!validTypes.includes(contentType)) {
|
|
302
|
-
return { valid: false, error: `contentType must be one of: ${validTypes.join(', ')}` };
|
|
303
|
-
}
|
|
304
|
-
const isTextual = contentType.startsWith('text/') || contentType.includes('markdown');
|
|
305
|
-
if (isTextual) {
|
|
306
|
-
try {
|
|
307
|
-
const maxBytes = 50 * 1024;
|
|
308
|
-
const bytes = (typeof TextEncoder !== 'undefined')
|
|
309
|
-
? new TextEncoder().encode(data.content).length
|
|
310
|
-
: String(data.content).length;
|
|
311
|
-
if (bytes > maxBytes) {
|
|
312
|
-
return {
|
|
313
|
-
valid: false,
|
|
314
|
-
error: `content exceeds ${maxBytes} bytes limit for ai-content-moderation verifier (text)`
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
} catch {
|
|
318
|
-
void 0;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
if (data.content.length > 13653334) {
|
|
323
|
-
return { valid: false, error: 'content exceeds 10MB limit' };
|
|
324
|
-
}
|
|
325
|
-
break;
|
|
326
|
-
case 'ownership-pseudonym':
|
|
327
|
-
if (!data.pseudonymId || typeof data.pseudonymId !== 'string') {
|
|
328
|
-
return { valid: false, error: 'pseudonymId is required' };
|
|
329
|
-
}
|
|
330
|
-
if (!/^[a-z0-9._-]{3,64}$/.test(data.pseudonymId.trim().toLowerCase())) {
|
|
331
|
-
return { valid: false, error: 'pseudonymId must be 3-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
|
|
332
|
-
}
|
|
333
|
-
if (data.namespace && typeof data.namespace === 'string') {
|
|
334
|
-
if (!/^[a-z0-9._-]{1,64}$/.test(data.namespace.trim().toLowerCase())) {
|
|
335
|
-
return { valid: false, error: 'namespace must be 1-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
break;
|
|
339
|
-
case 'wallet-risk':
|
|
340
|
-
if (data.walletAddress && !validateUniversalAddress(data.walletAddress, data.chain)) {
|
|
341
|
-
return { valid: false, error: 'Invalid walletAddress' };
|
|
342
|
-
}
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return { valid: true };
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
export class NeusClient {
|
|
350
|
-
constructor(config = {}) {
|
|
351
|
-
this.config = {
|
|
352
|
-
timeout: 30000,
|
|
353
|
-
enableLogging: false,
|
|
354
|
-
...config
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
this.baseUrl = this.config.apiUrl || 'https://api.neus.network';
|
|
358
|
-
try {
|
|
359
|
-
const url = new URL(this.baseUrl);
|
|
360
|
-
if (url.hostname.endsWith('neus.network') && url.protocol === 'http:') {
|
|
361
|
-
url.protocol = 'https:';
|
|
362
|
-
}
|
|
363
|
-
this.baseUrl = url.toString().replace(/\/$/, '');
|
|
364
|
-
} catch {
|
|
365
|
-
void 0;
|
|
366
|
-
}
|
|
367
|
-
this.config.apiUrl = this.baseUrl;
|
|
368
|
-
|
|
369
|
-
this.defaultHeaders = {
|
|
370
|
-
'Content-Type': 'application/json',
|
|
371
|
-
'Accept': 'application/json',
|
|
372
|
-
'X-Neus-Sdk': 'js'
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
if (typeof this.config.apiKey === 'string' && this.config.apiKey.trim().length > 0) {
|
|
376
|
-
this.defaultHeaders['Authorization'] = `Bearer ${this.config.apiKey.trim()}`;
|
|
377
|
-
}
|
|
378
|
-
if (typeof this.config.appId === 'string' && this.config.appId.trim().length > 0) {
|
|
379
|
-
this.defaultHeaders['X-Neus-App'] = this.config.appId.trim();
|
|
380
|
-
}
|
|
381
|
-
if (typeof this.config.paymentSignature === 'string' && this.config.paymentSignature.trim().length > 0) {
|
|
382
|
-
this.defaultHeaders['PAYMENT-SIGNATURE'] = this.config.paymentSignature.trim();
|
|
383
|
-
}
|
|
384
|
-
if (this.config.extraHeaders && typeof this.config.extraHeaders === 'object') {
|
|
385
|
-
for (const [k, v] of Object.entries(this.config.extraHeaders)) {
|
|
386
|
-
if (!k || v === undefined || v === null) continue;
|
|
387
|
-
const key = String(k).trim();
|
|
388
|
-
const value = String(v).trim();
|
|
389
|
-
if (!key || !value) continue;
|
|
390
|
-
this.defaultHeaders[key] = value;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
try {
|
|
394
|
-
if (typeof window !== 'undefined' && window.location && window.location.origin) {
|
|
395
|
-
this.defaultHeaders['X-Client-Origin'] = window.location.origin;
|
|
396
|
-
}
|
|
397
|
-
} catch {
|
|
398
|
-
void 0;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/** @type {{ token: string, expMs: number, key: string } | null} */
|
|
402
|
-
this._sponsorGrantCache = null;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
_getBillingWallet() {
|
|
406
|
-
const raw =
|
|
407
|
-
this.config.billingWallet ||
|
|
408
|
-
this.config.sponsorOrgWallet ||
|
|
409
|
-
this.config.orgWallet ||
|
|
410
|
-
null;
|
|
411
|
-
if (typeof raw !== 'string') return null;
|
|
412
|
-
const trimmed = raw.trim().toLowerCase();
|
|
413
|
-
return /^0x[a-f0-9]{40}$/.test(trimmed) ? trimmed : null;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
_resolveIntegratorOrigin() {
|
|
417
|
-
if (typeof this.config.appOrigin === 'string' && this.config.appOrigin.trim()) {
|
|
418
|
-
return this.config.appOrigin.trim();
|
|
419
|
-
}
|
|
420
|
-
try {
|
|
421
|
-
if (typeof window !== 'undefined' && window.location?.origin) {
|
|
422
|
-
return window.location.origin;
|
|
423
|
-
}
|
|
424
|
-
} catch {
|
|
425
|
-
void 0;
|
|
426
|
-
}
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
async _resolveSponsorGrantHeaders(verifierIds = []) {
|
|
431
|
-
const appId = typeof this.config.appId === 'string' ? this.config.appId.trim() : '';
|
|
432
|
-
const orgWallet = this._getBillingWallet();
|
|
433
|
-
if (!appId || !orgWallet) {
|
|
434
|
-
return {};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const normalizedVerifierIds = Array.isArray(verifierIds)
|
|
438
|
-
? verifierIds.map((v) => String(v || '').trim()).filter(Boolean).slice(0, 25)
|
|
439
|
-
: [];
|
|
440
|
-
const cacheKey = `${appId}:${orgWallet}:${normalizedVerifierIds.join(',')}`;
|
|
441
|
-
const now = Date.now();
|
|
442
|
-
if (
|
|
443
|
-
this._sponsorGrantCache &&
|
|
444
|
-
this._sponsorGrantCache.key === cacheKey &&
|
|
445
|
-
this._sponsorGrantCache.expMs > now + 30_000
|
|
446
|
-
) {
|
|
447
|
-
return { 'X-Sponsor-Grant': this._sponsorGrantCache.token };
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const origin = this._resolveIntegratorOrigin();
|
|
451
|
-
const grant = await fetchSponsorGrant({
|
|
452
|
-
apiUrl: this.baseUrl,
|
|
453
|
-
appId,
|
|
454
|
-
orgWallet,
|
|
455
|
-
verifierIds: normalizedVerifierIds,
|
|
456
|
-
origin
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
const expSeconds = Number(grant?.exp);
|
|
460
|
-
const expMs = Number.isFinite(expSeconds) && expSeconds > 0
|
|
461
|
-
? expSeconds * 1000
|
|
462
|
-
: now + 15 * 60 * 1000;
|
|
463
|
-
|
|
464
|
-
this._sponsorGrantCache = {
|
|
465
|
-
key: cacheKey,
|
|
466
|
-
token: grant.sponsorGrant,
|
|
467
|
-
expMs
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
return { 'X-Sponsor-Grant': grant.sponsorGrant };
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
_getHubChainId() {
|
|
474
|
-
const configured = Number(this.config?.hubChainId);
|
|
475
|
-
if (Number.isFinite(configured) && configured > 0) return Math.floor(configured);
|
|
476
|
-
return NEUS_CONSTANTS.HUB_CHAIN_ID;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
_normalizeIdentity(value) {
|
|
480
|
-
let raw = String(value || '').trim();
|
|
481
|
-
if (!raw) return '';
|
|
482
|
-
const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
|
|
483
|
-
if (didMatch && didMatch[3]) {
|
|
484
|
-
raw = String(didMatch[3]).trim();
|
|
485
|
-
}
|
|
486
|
-
return EVM_ADDRESS_RE.test(raw) ? raw.toLowerCase() : raw;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
_inferChainForAddress(address, explicitChain) {
|
|
490
|
-
if (typeof explicitChain === 'string' && explicitChain.includes(':')) return explicitChain.trim();
|
|
491
|
-
const raw = String(address || '').trim();
|
|
492
|
-
const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
|
|
493
|
-
if (didMatch && didMatch[1] && didMatch[2]) {
|
|
494
|
-
return `${didMatch[1]}:${didMatch[2]}`;
|
|
495
|
-
}
|
|
496
|
-
if (EVM_ADDRESS_RE.test(raw)) {
|
|
497
|
-
return `eip155:${this._getHubChainId()}`;
|
|
498
|
-
}
|
|
499
|
-
return 'solana:mainnet';
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
async _resolveWalletSigner(wallet) {
|
|
503
|
-
if (!wallet) {
|
|
504
|
-
throw new ConfigurationError('No wallet provider available');
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (wallet.address !== null && wallet.address !== undefined) {
|
|
508
|
-
const s = normalizeBrowserSignerString(
|
|
509
|
-
typeof wallet.address === 'string' ? wallet.address : null
|
|
510
|
-
);
|
|
511
|
-
if (s) {
|
|
512
|
-
return { signerWalletAddress: s, provider: wallet };
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
if (wallet.publicKey && typeof wallet.publicKey.toBase58 === 'function') {
|
|
516
|
-
const b58 = wallet.publicKey.toBase58();
|
|
517
|
-
const s = normalizeBrowserSignerString(typeof b58 === 'string' ? b58 : null);
|
|
518
|
-
if (s) {
|
|
519
|
-
return { signerWalletAddress: s, provider: wallet };
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
if (typeof wallet.getAddress === 'function') {
|
|
523
|
-
const got = await wallet.getAddress().catch(() => null);
|
|
524
|
-
const s = normalizeBrowserSignerString(
|
|
525
|
-
got === null || got === undefined ? null : (typeof got === 'string' ? got : null)
|
|
526
|
-
);
|
|
527
|
-
if (s) {
|
|
528
|
-
return { signerWalletAddress: s, provider: wallet };
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
if (wallet.selectedAddress || wallet.request) {
|
|
532
|
-
const provider = wallet;
|
|
533
|
-
if (wallet.selectedAddress) {
|
|
534
|
-
const s = normalizeBrowserSignerString(
|
|
535
|
-
typeof wallet.selectedAddress === 'string' ? wallet.selectedAddress : null
|
|
536
|
-
);
|
|
537
|
-
if (s) {
|
|
538
|
-
return { signerWalletAddress: s, provider };
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
const accounts = await provider.request({ method: 'eth_accounts' });
|
|
542
|
-
if (!accounts || accounts.length === 0) {
|
|
543
|
-
throw new ConfigurationError('No wallet accounts available');
|
|
544
|
-
}
|
|
545
|
-
const s = normalizeBrowserSignerString(accounts[0]);
|
|
546
|
-
if (!s) {
|
|
547
|
-
throw new ConfigurationError('No wallet accounts available');
|
|
548
|
-
}
|
|
549
|
-
return { signerWalletAddress: s, provider };
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
throw new ConfigurationError('Invalid wallet provider');
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
_getDefaultBrowserWallet() {
|
|
556
|
-
if (typeof window === 'undefined') return null;
|
|
557
|
-
// Legacy convenience fallback only. Non-EVM wallets must be passed explicitly
|
|
558
|
-
// with CAIP-2 chain context so the SDK does not route them through EVM RPC.
|
|
559
|
-
return window.ethereum || null;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
|
|
563
|
-
const providerWallet = wallet || this._getDefaultBrowserWallet();
|
|
564
|
-
const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
|
|
565
|
-
if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
|
|
566
|
-
throw new ConfigurationError('No wallet accounts available');
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
|
|
570
|
-
const normalizedAddress = this._normalizeIdentity(address);
|
|
571
|
-
if (!normalizedSigner || normalizedSigner !== normalizedAddress) {
|
|
572
|
-
throw new ValidationError('wallet must match address when includePrivate=true');
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
|
|
576
|
-
const resolvedChain = this._inferChainForAddress(normalizedSigner, chain);
|
|
577
|
-
const resolvedSignatureMethod = (typeof signatureMethod === 'string' && signatureMethod.trim())
|
|
578
|
-
? signatureMethod.trim()
|
|
579
|
-
: (signerIsEvm ? 'eip191' : 'ed25519');
|
|
580
|
-
|
|
581
|
-
const signedTimestamp = Date.now();
|
|
582
|
-
const message = constructVerificationMessage({
|
|
583
|
-
walletAddress: signerWalletAddress,
|
|
584
|
-
signedTimestamp,
|
|
585
|
-
data: { action: 'gate_check_private_proofs', walletAddress: normalizedAddress },
|
|
586
|
-
verifierIds: ['ownership-basic'],
|
|
587
|
-
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain: resolvedChain })
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
let signature;
|
|
591
|
-
try {
|
|
592
|
-
signature = await signMessage({
|
|
593
|
-
provider,
|
|
594
|
-
message,
|
|
595
|
-
walletAddress: signerWalletAddress,
|
|
596
|
-
...(signerIsEvm ? {} : { chain: resolvedChain })
|
|
597
|
-
});
|
|
598
|
-
} catch (error) {
|
|
599
|
-
if (error.code === 4001) {
|
|
600
|
-
throw new ValidationError('User rejected signature request');
|
|
601
|
-
}
|
|
602
|
-
throw new ValidationError(`Failed to sign message: ${error.message}`);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
return {
|
|
606
|
-
walletAddress: signerWalletAddress,
|
|
607
|
-
signature,
|
|
608
|
-
signedTimestamp,
|
|
609
|
-
...(signerIsEvm ? {} : { chain: resolvedChain, signatureMethod: resolvedSignatureMethod })
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
async createGatePrivateAuth(params = {}) {
|
|
614
|
-
const address = (params.address || '').toString();
|
|
615
|
-
if (!validateUniversalAddress(address, params.chain)) {
|
|
616
|
-
throw new ValidationError('Valid address is required');
|
|
617
|
-
}
|
|
618
|
-
return this._buildPrivateGateAuth({
|
|
619
|
-
address,
|
|
620
|
-
wallet: params.wallet,
|
|
621
|
-
chain: params.chain,
|
|
622
|
-
signatureMethod: params.signatureMethod
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
async createWalletLinkData(params = {}) {
|
|
627
|
-
const normalizedPrimary = this._normalizeIdentity(params.primaryWalletAddress);
|
|
628
|
-
const normalizedSecondary = this._normalizeIdentity(params.secondaryWalletAddress);
|
|
629
|
-
|
|
630
|
-
if (!EVM_ADDRESS_RE.test(normalizedPrimary)) {
|
|
631
|
-
throw new ValidationError('wallet-link requires a valid primaryWalletAddress');
|
|
632
|
-
}
|
|
633
|
-
if (!EVM_ADDRESS_RE.test(normalizedSecondary)) {
|
|
634
|
-
throw new ValidationError('wallet-link requires a valid secondaryWalletAddress');
|
|
635
|
-
}
|
|
636
|
-
if (normalizedPrimary === normalizedSecondary) {
|
|
637
|
-
throw new ValidationError('wallet-link secondaryWalletAddress must differ from primaryWalletAddress');
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
const providerWallet = params.wallet || this._getDefaultBrowserWallet();
|
|
641
|
-
const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
|
|
642
|
-
const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
|
|
643
|
-
if (!EVM_ADDRESS_RE.test(normalizedSigner)) {
|
|
644
|
-
throw new ValidationError('wallet-link requires an EVM wallet/provider for the secondary wallet');
|
|
645
|
-
}
|
|
646
|
-
if (normalizedSigner !== normalizedSecondary) {
|
|
647
|
-
throw new ValidationError('wallet-link wallet/provider must be connected to secondaryWalletAddress');
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
const resolvedChain = (() => {
|
|
651
|
-
const raw = String(params.chain || '').trim();
|
|
652
|
-
if (!raw) return `eip155:${this._getHubChainId()}`;
|
|
653
|
-
if (!/^eip155:\d+$/.test(raw)) {
|
|
654
|
-
throw new ValidationError('wallet-link requires chain in the form eip155:<chainId>');
|
|
655
|
-
}
|
|
656
|
-
return raw;
|
|
657
|
-
})();
|
|
658
|
-
const signedTimestamp = Number.isFinite(Number(params.signedTimestamp)) && Number(params.signedTimestamp) > 0
|
|
659
|
-
? Number(params.signedTimestamp)
|
|
660
|
-
: Date.now();
|
|
661
|
-
const linkData = {
|
|
662
|
-
primaryAccountId: `${resolvedChain}:${normalizedPrimary}`,
|
|
663
|
-
secondaryAccountId: `${resolvedChain}:${normalizedSecondary}`,
|
|
664
|
-
primaryWalletAddress: normalizedPrimary,
|
|
665
|
-
secondaryWalletAddress: normalizedSecondary
|
|
666
|
-
};
|
|
667
|
-
const message = constructVerificationMessage({
|
|
668
|
-
walletAddress: normalizedSecondary,
|
|
669
|
-
signedTimestamp,
|
|
670
|
-
data: linkData,
|
|
671
|
-
verifierIds: ['wallet-link'],
|
|
672
|
-
chain: resolvedChain
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
let signature;
|
|
676
|
-
try {
|
|
677
|
-
signature = await signMessage({
|
|
678
|
-
provider,
|
|
679
|
-
message,
|
|
680
|
-
walletAddress: signerWalletAddress
|
|
681
|
-
});
|
|
682
|
-
} catch (error) {
|
|
683
|
-
if (error?.code === 4001) {
|
|
684
|
-
throw new ValidationError('User rejected wallet-link signature request');
|
|
685
|
-
}
|
|
686
|
-
throw new ValidationError(`Failed to sign wallet-link message: ${error?.message || String(error)}`);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
const label = String(params.label || '').trim().slice(0, 64);
|
|
690
|
-
return {
|
|
691
|
-
primaryWalletAddress: normalizedPrimary,
|
|
692
|
-
secondaryWalletAddress: normalizedSecondary,
|
|
693
|
-
signature,
|
|
694
|
-
chain: resolvedChain,
|
|
695
|
-
signatureMethod: 'eip191',
|
|
696
|
-
signedTimestamp,
|
|
697
|
-
relationshipType: normalizeWalletLinkRelationshipType(params.relationshipType),
|
|
698
|
-
...(label ? { label } : {})
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
async verifyFromApp(params) {
|
|
703
|
-
if (!params || typeof params !== 'object') {
|
|
704
|
-
throw new ValidationError('verifyFromApp requires a params object');
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
const {
|
|
708
|
-
user,
|
|
709
|
-
verifier = 'ownership-basic',
|
|
710
|
-
content,
|
|
711
|
-
data: explicitData = null,
|
|
712
|
-
options = {}
|
|
713
|
-
} = params;
|
|
714
|
-
|
|
715
|
-
if (!user || typeof user !== 'object') {
|
|
716
|
-
throw new ValidationError('verifyFromApp requires user object');
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
const walletAddress =
|
|
720
|
-
user.walletAddress ||
|
|
721
|
-
user.address ||
|
|
722
|
-
user.identity;
|
|
723
|
-
if (!walletAddress || typeof walletAddress !== 'string') {
|
|
724
|
-
throw new ValidationError('verifyFromApp requires user.walletAddress');
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
const delegationQHash =
|
|
728
|
-
this.config.appLinkQHash ||
|
|
729
|
-
(typeof process !== 'undefined' && process.env && process.env.NEUS_APP_LINK_QHASH) ||
|
|
730
|
-
null;
|
|
731
|
-
|
|
732
|
-
let data;
|
|
733
|
-
if (explicitData && typeof explicitData === 'object') {
|
|
734
|
-
data = { owner: walletAddress, ...explicitData };
|
|
735
|
-
} else if (content && typeof content === 'object') {
|
|
736
|
-
data = {
|
|
737
|
-
owner: walletAddress,
|
|
738
|
-
content: JSON.stringify(content),
|
|
739
|
-
contentType: typeof content.contentType === 'string' ? content.contentType : 'application/json',
|
|
740
|
-
reference: content.reference || { type: 'other' }
|
|
741
|
-
};
|
|
742
|
-
} else if (typeof content === 'string') {
|
|
743
|
-
data = {
|
|
744
|
-
owner: walletAddress,
|
|
745
|
-
content,
|
|
746
|
-
reference: { type: 'other' }
|
|
747
|
-
};
|
|
748
|
-
} else {
|
|
749
|
-
data = { owner: walletAddress, reference: { type: 'other' } };
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
const signedTimestamp = Date.now();
|
|
753
|
-
const verifierIds = [verifier];
|
|
754
|
-
|
|
755
|
-
return this.verify({
|
|
756
|
-
verifierIds,
|
|
757
|
-
data,
|
|
758
|
-
walletAddress,
|
|
759
|
-
signedTimestamp,
|
|
760
|
-
...(delegationQHash ? { delegationQHash } : {}),
|
|
761
|
-
options
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
async verify(params) {
|
|
766
|
-
if (
|
|
767
|
-
(isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) &&
|
|
768
|
-
(params?.verifier || params?.content || params?.data)
|
|
769
|
-
) {
|
|
770
|
-
const { content, verifier = 'ownership-basic', data = null, wallet = null, options = {} } = params;
|
|
771
|
-
|
|
772
|
-
if (verifier === 'ownership-basic' && !data && (!content || typeof content !== 'string')) {
|
|
773
|
-
throw new ValidationError('content is required and must be a string (or use data param with owner + reference)');
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
let verifierCatalog = FALLBACK_PUBLIC_VERIFIER_CATALOG;
|
|
777
|
-
try {
|
|
778
|
-
const discovered = await this.getVerifierCatalog();
|
|
779
|
-
if (discovered && discovered.metadata && Object.keys(discovered.metadata).length > 0) {
|
|
780
|
-
verifierCatalog = discovered.metadata;
|
|
781
|
-
}
|
|
782
|
-
} catch {
|
|
783
|
-
void 0;
|
|
784
|
-
}
|
|
785
|
-
const validVerifiers = Object.keys(verifierCatalog);
|
|
786
|
-
if (!validVerifiers.includes(verifier)) {
|
|
787
|
-
throw new ValidationError(`Invalid verifier '${verifier}'. Must be one of: ${validVerifiers.join(', ')}.`);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
if (verifierCatalog?.[verifier]?.supportsDirectApi === false) {
|
|
791
|
-
const hostedCheckoutUrl = options?.hostedCheckoutUrl || 'https://neus.network/verify';
|
|
792
|
-
throw new ValidationError(
|
|
793
|
-
`${verifier} requires hosted interactive checkout. Use VerifyGate or redirect to ${hostedCheckoutUrl}.`
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
const requiresDataParam = [
|
|
798
|
-
'ownership-dns-txt',
|
|
799
|
-
'wallet-link',
|
|
800
|
-
'contract-ownership',
|
|
801
|
-
'ownership-pseudonym',
|
|
802
|
-
'wallet-risk',
|
|
803
|
-
'agent-identity',
|
|
804
|
-
'agent-delegation',
|
|
805
|
-
'ai-content-moderation'
|
|
806
|
-
];
|
|
807
|
-
if (requiresDataParam.includes(verifier)) {
|
|
808
|
-
if (!data || typeof data !== 'object') {
|
|
809
|
-
throw new ValidationError(`${verifier} requires explicit data parameter. Cannot use auto-path.`);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
let walletAddress, provider;
|
|
814
|
-
if (wallet) {
|
|
815
|
-
walletAddress =
|
|
816
|
-
wallet.address ||
|
|
817
|
-
wallet.selectedAddress ||
|
|
818
|
-
wallet.walletAddress ||
|
|
819
|
-
(typeof wallet.getAddress === 'function' ? await wallet.getAddress().catch(() => null) : null);
|
|
820
|
-
provider = wallet.provider || wallet;
|
|
821
|
-
if (!walletAddress && provider && typeof provider.request === 'function') {
|
|
822
|
-
let accounts = await provider.request({ method: 'eth_accounts' });
|
|
823
|
-
walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
|
|
824
|
-
if (!walletAddress) {
|
|
825
|
-
await provider.request({ method: 'eth_requestAccounts' });
|
|
826
|
-
accounts = await provider.request({ method: 'eth_accounts' });
|
|
827
|
-
walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
} else {
|
|
831
|
-
if (typeof window === 'undefined' || !window.ethereum) {
|
|
832
|
-
throw new ConfigurationError('No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.');
|
|
833
|
-
}
|
|
834
|
-
await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
835
|
-
provider = window.ethereum;
|
|
836
|
-
const accounts = await provider.request({ method: 'eth_accounts' });
|
|
837
|
-
walletAddress = accounts[0];
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
{
|
|
841
|
-
const normalized = normalizeBrowserSignerString(
|
|
842
|
-
typeof walletAddress === 'string' ? walletAddress : null
|
|
843
|
-
);
|
|
844
|
-
if (!normalized) {
|
|
845
|
-
throw new ConfigurationError('No wallet accounts available. Connect a wallet to continue.');
|
|
846
|
-
}
|
|
847
|
-
walletAddress = normalized;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
let verificationData;
|
|
851
|
-
if (verifier === 'ownership-basic') {
|
|
852
|
-
if (data && typeof data === 'object') {
|
|
853
|
-
verificationData = {
|
|
854
|
-
owner: data.owner || walletAddress,
|
|
855
|
-
reference: data.reference,
|
|
856
|
-
...(data.content && { content: data.content }),
|
|
857
|
-
...(data.contentHash && { contentHash: data.contentHash }),
|
|
858
|
-
...(data.contentType && { contentType: data.contentType }),
|
|
859
|
-
...(data.provenance && { provenance: data.provenance })
|
|
860
|
-
};
|
|
861
|
-
} else {
|
|
862
|
-
verificationData = {
|
|
863
|
-
content: content,
|
|
864
|
-
owner: walletAddress,
|
|
865
|
-
reference: { type: 'other' }
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
} else if (verifier === 'token-holding') {
|
|
869
|
-
if (!data?.contractAddress) {
|
|
870
|
-
throw new ValidationError('token-holding requires contractAddress in data parameter');
|
|
871
|
-
}
|
|
872
|
-
if (data?.minBalance === null || data?.minBalance === undefined) {
|
|
873
|
-
throw new ValidationError('token-holding requires minBalance in data parameter');
|
|
874
|
-
}
|
|
875
|
-
if (typeof data?.chainId !== 'number') {
|
|
876
|
-
throw new ValidationError('token-holding requires chainId (number) in data parameter');
|
|
877
|
-
}
|
|
878
|
-
verificationData = {
|
|
879
|
-
ownerAddress: walletAddress,
|
|
880
|
-
contractAddress: data.contractAddress,
|
|
881
|
-
minBalance: data.minBalance,
|
|
882
|
-
chainId: data.chainId
|
|
883
|
-
};
|
|
884
|
-
} else if (verifier === 'nft-ownership') {
|
|
885
|
-
if (!data?.contractAddress) {
|
|
886
|
-
throw new ValidationError('nft-ownership requires contractAddress in data parameter');
|
|
887
|
-
}
|
|
888
|
-
if (data?.tokenId === null || data?.tokenId === undefined) {
|
|
889
|
-
throw new ValidationError('nft-ownership requires tokenId in data parameter');
|
|
890
|
-
}
|
|
891
|
-
if (typeof data?.chainId !== 'number') {
|
|
892
|
-
throw new ValidationError('nft-ownership requires chainId (number) in data parameter');
|
|
893
|
-
}
|
|
894
|
-
verificationData = {
|
|
895
|
-
ownerAddress: walletAddress,
|
|
896
|
-
contractAddress: data.contractAddress,
|
|
897
|
-
tokenId: data.tokenId,
|
|
898
|
-
chainId: data.chainId,
|
|
899
|
-
tokenType: data?.tokenType || 'erc721'
|
|
900
|
-
};
|
|
901
|
-
} else if (verifier === 'ownership-dns-txt') {
|
|
902
|
-
if (!data?.domain) {
|
|
903
|
-
throw new ValidationError('ownership-dns-txt requires domain in data parameter');
|
|
904
|
-
}
|
|
905
|
-
verificationData = {
|
|
906
|
-
domain: data.domain,
|
|
907
|
-
walletAddress: walletAddress
|
|
908
|
-
};
|
|
909
|
-
} else if (verifier === 'wallet-link') {
|
|
910
|
-
if (!data?.secondaryWalletAddress) {
|
|
911
|
-
throw new ValidationError('wallet-link requires secondaryWalletAddress in data parameter');
|
|
912
|
-
}
|
|
913
|
-
if (!data?.signature) {
|
|
914
|
-
throw new ValidationError('wallet-link requires signature in data parameter (signed by secondary wallet)');
|
|
915
|
-
}
|
|
916
|
-
if (typeof data?.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
|
|
917
|
-
throw new ValidationError('wallet-link requires chain (namespace:reference) in data parameter');
|
|
918
|
-
}
|
|
919
|
-
if (typeof data?.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
|
|
920
|
-
throw new ValidationError('wallet-link requires signatureMethod in data parameter');
|
|
921
|
-
}
|
|
922
|
-
verificationData = {
|
|
923
|
-
primaryWalletAddress: walletAddress,
|
|
924
|
-
secondaryWalletAddress: data.secondaryWalletAddress,
|
|
925
|
-
signature: data.signature,
|
|
926
|
-
chain: data.chain,
|
|
927
|
-
signatureMethod: data.signatureMethod,
|
|
928
|
-
signedTimestamp: data?.signedTimestamp || Date.now()
|
|
929
|
-
};
|
|
930
|
-
} else if (verifier === 'contract-ownership') {
|
|
931
|
-
if (!data?.contractAddress) {
|
|
932
|
-
throw new ValidationError('contract-ownership requires contractAddress in data parameter');
|
|
933
|
-
}
|
|
934
|
-
if (typeof data?.chainId !== 'number') {
|
|
935
|
-
throw new ValidationError('contract-ownership requires chainId (number) in data parameter');
|
|
936
|
-
}
|
|
937
|
-
verificationData = {
|
|
938
|
-
contractAddress: data.contractAddress,
|
|
939
|
-
walletAddress: walletAddress,
|
|
940
|
-
chainId: data.chainId,
|
|
941
|
-
...(data?.method && { method: data.method })
|
|
942
|
-
};
|
|
943
|
-
} else if (verifier === 'agent-identity') {
|
|
944
|
-
if (!data?.agentId) {
|
|
945
|
-
throw new ValidationError('agent-identity requires agentId in data parameter');
|
|
946
|
-
}
|
|
947
|
-
verificationData = {
|
|
948
|
-
agentId: data.agentId,
|
|
949
|
-
agentWallet: data?.agentWallet || walletAddress,
|
|
950
|
-
...(data?.agentChainRef && { agentChainRef: data.agentChainRef }),
|
|
951
|
-
...(data?.agentAccountId && { agentAccountId: data.agentAccountId }),
|
|
952
|
-
...(data?.agentLabel && { agentLabel: data.agentLabel }),
|
|
953
|
-
...(data?.agentType && { agentType: data.agentType }),
|
|
954
|
-
...(data?.avatar && { avatar: data.avatar }),
|
|
955
|
-
...(data?.description && { description: data.description }),
|
|
956
|
-
...(data?.defaultRuntime && { defaultRuntime: data.defaultRuntime }),
|
|
957
|
-
...(data?.capabilities && { capabilities: data.capabilities }),
|
|
958
|
-
...(data?.instructions && { instructions: data.instructions }),
|
|
959
|
-
...(data?.skills && { skills: data.skills }),
|
|
960
|
-
...(data?.services && { services: data.services })
|
|
961
|
-
};
|
|
962
|
-
} else if (verifier === 'agent-delegation') {
|
|
963
|
-
if (!data?.agentWallet) {
|
|
964
|
-
throw new ValidationError('agent-delegation requires agentWallet in data parameter');
|
|
965
|
-
}
|
|
966
|
-
verificationData = {
|
|
967
|
-
controllerWallet: data?.controllerWallet || walletAddress,
|
|
968
|
-
...(data?.controllerChainRef && { controllerChainRef: data.controllerChainRef }),
|
|
969
|
-
agentWallet: data.agentWallet,
|
|
970
|
-
...(data?.agentChainRef && { agentChainRef: data.agentChainRef }),
|
|
971
|
-
...(data?.controllerAccountId && { controllerAccountId: data.controllerAccountId }),
|
|
972
|
-
...(data?.agentAccountId && { agentAccountId: data.agentAccountId }),
|
|
973
|
-
...(data?.agentId && { agentId: data.agentId }),
|
|
974
|
-
...(data?.scope && { scope: data.scope }),
|
|
975
|
-
...(data?.permissions && { permissions: data.permissions }),
|
|
976
|
-
...(data?.maxSpend && { maxSpend: data.maxSpend }),
|
|
977
|
-
...(data?.allowedPaymentTypes && { allowedPaymentTypes: data.allowedPaymentTypes }),
|
|
978
|
-
...(data?.receiptDisclosure && { receiptDisclosure: data.receiptDisclosure }),
|
|
979
|
-
...(data?.expiresAt && { expiresAt: data.expiresAt }),
|
|
980
|
-
...(data?.instructions && { instructions: data.instructions }),
|
|
981
|
-
...(data?.skills && { skills: data.skills }),
|
|
982
|
-
...(data?.model && { model: data.model }),
|
|
983
|
-
...(data?.provider && { provider: data.provider }),
|
|
984
|
-
...(data?.runtimePolicy && { runtimePolicy: data.runtimePolicy }),
|
|
985
|
-
...(data?.allowedActions && { allowedActions: data.allowedActions }),
|
|
986
|
-
...(data?.deniedActions && { deniedActions: data.deniedActions }),
|
|
987
|
-
...(data?.approvalPolicy && { approvalPolicy: data.approvalPolicy })
|
|
988
|
-
};
|
|
989
|
-
} else if (verifier === 'ai-content-moderation') {
|
|
990
|
-
if (!data?.content) {
|
|
991
|
-
throw new ValidationError('ai-content-moderation requires content (base64) in data parameter');
|
|
992
|
-
}
|
|
993
|
-
if (!data?.contentType) {
|
|
994
|
-
throw new ValidationError('ai-content-moderation requires contentType (MIME type) in data parameter');
|
|
995
|
-
}
|
|
996
|
-
verificationData = {
|
|
997
|
-
content: data.content,
|
|
998
|
-
contentType: data.contentType,
|
|
999
|
-
...(data?.provider && { provider: data.provider })
|
|
1000
|
-
};
|
|
1001
|
-
} else if (verifier === 'ownership-pseudonym') {
|
|
1002
|
-
if (!data?.pseudonymId) {
|
|
1003
|
-
throw new ValidationError('ownership-pseudonym requires pseudonymId in data parameter');
|
|
1004
|
-
}
|
|
1005
|
-
verificationData = {
|
|
1006
|
-
pseudonymId: data.pseudonymId,
|
|
1007
|
-
...(data?.namespace && { namespace: data.namespace }),
|
|
1008
|
-
...(data?.displayName && { displayName: data.displayName }),
|
|
1009
|
-
...(data?.metadata && { metadata: data.metadata })
|
|
1010
|
-
};
|
|
1011
|
-
} else if (verifier === 'wallet-risk') {
|
|
1012
|
-
verificationData = {
|
|
1013
|
-
walletAddress: data?.walletAddress || walletAddress,
|
|
1014
|
-
...(data?.provider && { provider: data.provider }),
|
|
1015
|
-
chainId: (typeof data?.chainId === 'number' && Number.isFinite(data.chainId)) ? data.chainId : 1,
|
|
1016
|
-
...(data?.includeDetails !== undefined && { includeDetails: data.includeDetails })
|
|
1017
|
-
};
|
|
1018
|
-
} else {
|
|
1019
|
-
verificationData = data ? {
|
|
1020
|
-
content,
|
|
1021
|
-
owner: walletAddress,
|
|
1022
|
-
...data
|
|
1023
|
-
} : {
|
|
1024
|
-
content,
|
|
1025
|
-
owner: walletAddress
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
const signedTimestamp = Date.now();
|
|
1030
|
-
const verifierIds = [verifier];
|
|
1031
|
-
const message = constructVerificationMessage({
|
|
1032
|
-
walletAddress,
|
|
1033
|
-
signedTimestamp,
|
|
1034
|
-
data: verificationData,
|
|
1035
|
-
verifierIds,
|
|
1036
|
-
chainId: this._getHubChainId()
|
|
1037
|
-
});
|
|
1038
|
-
|
|
1039
|
-
let signature;
|
|
1040
|
-
try {
|
|
1041
|
-
const toHexUtf8 = (s) => {
|
|
1042
|
-
try {
|
|
1043
|
-
const enc = new TextEncoder();
|
|
1044
|
-
const bytes = enc.encode(s);
|
|
1045
|
-
let hex = '0x';
|
|
1046
|
-
for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, '0');
|
|
1047
|
-
return hex;
|
|
1048
|
-
} catch {
|
|
1049
|
-
let hex = '0x';
|
|
1050
|
-
for (let i = 0; i < s.length; i++) hex += s.charCodeAt(i).toString(16).padStart(2, '0');
|
|
1051
|
-
return hex;
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
|
|
1055
|
-
const isBaseMiniAppWallet = (() => {
|
|
1056
|
-
if (typeof window === 'undefined') return false;
|
|
1057
|
-
try {
|
|
1058
|
-
const w = window;
|
|
1059
|
-
const mini = w.mini;
|
|
1060
|
-
if (!mini) return false;
|
|
1061
|
-
const miniProvider = mini.wallet || mini.provider;
|
|
1062
|
-
if (miniProvider === provider) return true;
|
|
1063
|
-
if (w.ethereum === provider && mini) return true;
|
|
1064
|
-
} catch {
|
|
1065
|
-
void 0;
|
|
1066
|
-
}
|
|
1067
|
-
return false;
|
|
1068
|
-
})();
|
|
1069
|
-
|
|
1070
|
-
if (isBaseMiniAppWallet) {
|
|
1071
|
-
try {
|
|
1072
|
-
const hexMsg = toHexUtf8(message);
|
|
1073
|
-
signature = await provider.request({ method: 'personal_sign', params: [hexMsg, walletAddress] });
|
|
1074
|
-
} catch (e) {
|
|
1075
|
-
void e;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
if (!signature) {
|
|
1080
|
-
try {
|
|
1081
|
-
signature = await provider.request({ method: 'personal_sign', params: [message, walletAddress] });
|
|
1082
|
-
} catch (e) {
|
|
1083
|
-
const msg = String(e && (e.message || e.reason) || e || '').toLowerCase();
|
|
1084
|
-
const errCode = (e && (e.code || (e.error && e.error.code))) || null;
|
|
1085
|
-
const needsHex = /byte|bytes|invalid byte sequence|encoding|non-hex/i.test(msg);
|
|
1086
|
-
|
|
1087
|
-
const unsupportedRe =
|
|
1088
|
-
/method.*not.*supported|unsupported|not implemented|method not found|unknown method|does not support/i;
|
|
1089
|
-
const methodUnsupported = (
|
|
1090
|
-
unsupportedRe.test(msg) ||
|
|
1091
|
-
errCode === -32601 ||
|
|
1092
|
-
errCode === 4200 ||
|
|
1093
|
-
(msg.includes('personal_sign') && msg.includes('not')) ||
|
|
1094
|
-
(msg.includes('request method') && msg.includes('not supported'))
|
|
1095
|
-
);
|
|
1096
|
-
|
|
1097
|
-
if (methodUnsupported) {
|
|
1098
|
-
this._log('personal_sign not supported; attempting eth_sign fallback');
|
|
1099
|
-
try {
|
|
1100
|
-
const enc = new TextEncoder();
|
|
1101
|
-
const bytes = enc.encode(message);
|
|
1102
|
-
const prefix = `\x19Ethereum Signed Message:\n${bytes.length}`;
|
|
1103
|
-
const full = new Uint8Array(prefix.length + bytes.length);
|
|
1104
|
-
for (let i = 0; i < prefix.length; i++) full[i] = prefix.charCodeAt(i);
|
|
1105
|
-
full.set(bytes, prefix.length);
|
|
1106
|
-
let payloadHex = '0x';
|
|
1107
|
-
for (let i = 0; i < full.length; i++) payloadHex += full[i].toString(16).padStart(2, '0');
|
|
1108
|
-
try {
|
|
1109
|
-
if (typeof window !== 'undefined') window.__NEUS_ALLOW_ETH_SIGN__ = true;
|
|
1110
|
-
signature = await provider.request({ method: 'eth_sign', params: [walletAddress, payloadHex], neusAllowEthSign: true });
|
|
1111
|
-
} finally {
|
|
1112
|
-
try { if (typeof window !== 'undefined') delete window.__NEUS_ALLOW_ETH_SIGN__; } catch { void 0; }
|
|
1113
|
-
}
|
|
1114
|
-
} catch (fallbackErr) {
|
|
1115
|
-
this._log('eth_sign fallback failed', { message: fallbackErr?.message || String(fallbackErr) });
|
|
1116
|
-
throw e;
|
|
1117
|
-
}
|
|
1118
|
-
} else if (needsHex) {
|
|
1119
|
-
this._log('Retrying personal_sign with hex-encoded message');
|
|
1120
|
-
const hexMsg = toHexUtf8(message);
|
|
1121
|
-
signature = await provider.request({ method: 'personal_sign', params: [hexMsg, walletAddress] });
|
|
1122
|
-
} else {
|
|
1123
|
-
throw e;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
if (error.code === 4001) {
|
|
1129
|
-
throw new ValidationError('User rejected the signature request. Signature is required to create proofs.');
|
|
1130
|
-
}
|
|
1131
|
-
throw new ValidationError(`Failed to sign verification message: ${error.message}`);
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
return this.verify({
|
|
1135
|
-
verifierIds,
|
|
1136
|
-
data: verificationData,
|
|
1137
|
-
walletAddress,
|
|
1138
|
-
signature,
|
|
1139
|
-
signedTimestamp,
|
|
1140
|
-
options
|
|
1141
|
-
});
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
const {
|
|
1145
|
-
verifierIds,
|
|
1146
|
-
data,
|
|
1147
|
-
walletAddress,
|
|
1148
|
-
signature: rawSignature,
|
|
1149
|
-
signedTimestamp,
|
|
1150
|
-
chainId,
|
|
1151
|
-
chain,
|
|
1152
|
-
signatureMethod,
|
|
1153
|
-
delegationQHash,
|
|
1154
|
-
options = {}
|
|
1155
|
-
} = params;
|
|
1156
|
-
|
|
1157
|
-
const signature = isPlaceholderNeusSignature(rawSignature) ? undefined : rawSignature;
|
|
1158
|
-
|
|
1159
|
-
const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
|
|
1160
|
-
|
|
1161
|
-
const normalizeVerifierId = (id) => {
|
|
1162
|
-
if (typeof id !== 'string') return id;
|
|
1163
|
-
const match = id.match(/^(.*)@\d+$/);
|
|
1164
|
-
return match ? match[1] : id;
|
|
1165
|
-
};
|
|
1166
|
-
const normalizedVerifierIds = Array.isArray(verifierIds) ? verifierIds.map(normalizeVerifierId) : [];
|
|
1167
|
-
|
|
1168
|
-
if (!normalizedVerifierIds || normalizedVerifierIds.length === 0) {
|
|
1169
|
-
throw new ValidationError('verifierIds array is required');
|
|
1170
|
-
}
|
|
1171
|
-
if (!data || typeof data !== 'object') {
|
|
1172
|
-
throw new ValidationError('data object is required');
|
|
1173
|
-
}
|
|
1174
|
-
if (!walletAddress || typeof walletAddress !== 'string') {
|
|
1175
|
-
throw new ValidationError('walletAddress is required');
|
|
1176
|
-
}
|
|
1177
|
-
const hasAppAttribution =
|
|
1178
|
-
typeof this.config.appId === 'string' && this.config.appId.trim().length > 0;
|
|
1179
|
-
if (!signature && !delegationQHash && !hasAppAttribution) {
|
|
1180
|
-
throw new ValidationError(
|
|
1181
|
-
'signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required'
|
|
1182
|
-
);
|
|
1183
|
-
}
|
|
1184
|
-
if (!signedTimestamp || typeof signedTimestamp !== 'number') {
|
|
1185
|
-
throw new ValidationError('signedTimestamp is required');
|
|
1186
|
-
}
|
|
1187
|
-
if (resolvedChainId !== null && typeof resolvedChainId !== 'number') {
|
|
1188
|
-
throw new ValidationError('chainId must be a number');
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
for (const verifierId of normalizedVerifierIds) {
|
|
1192
|
-
const validation = validateVerifierData(verifierId, data);
|
|
1193
|
-
if (!validation.valid) {
|
|
1194
|
-
throw new ValidationError(`Validation failed for verifier '${verifierId}': ${validation.error}`);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
const optionsPayload = {
|
|
1199
|
-
...(options && typeof options === 'object' ? options : {}),
|
|
1200
|
-
targetChains: Array.isArray(options?.targetChains) ? options.targetChains : [],
|
|
1201
|
-
privacyLevel: options?.privacyLevel || 'private',
|
|
1202
|
-
publicDisplay: options?.publicDisplay || false,
|
|
1203
|
-
storeOriginalContent:
|
|
1204
|
-
typeof options?.storeOriginalContent === 'boolean' ? options.storeOriginalContent : true
|
|
1205
|
-
};
|
|
1206
|
-
if (typeof options?.enableIpfs === 'boolean') optionsPayload.enableIpfs = options.enableIpfs;
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
const
|
|
1280
|
-
const
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
throw new
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
const
|
|
1435
|
-
const
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
const json = await
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1482
|
-
const
|
|
1483
|
-
if (
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
if (options.
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
);
|
|
1494
|
-
|
|
1495
|
-
if (
|
|
1496
|
-
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
'
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
setBoolIfPresent(
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
setCsvIfPresent(
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
setIfPresent('
|
|
1666
|
-
setIfPresent('
|
|
1667
|
-
setIfPresent('
|
|
1668
|
-
|
|
1669
|
-
setBoolIfPresent('
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
setIfPresent('
|
|
1673
|
-
setIfPresent('
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
else
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
'
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
];
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
1
|
+
import { ApiError, ValidationError, NetworkError, ConfigurationError } from './errors.js';
|
|
2
|
+
import { fetchSponsorGrant } from './sponsor.js';
|
|
3
|
+
import {
|
|
4
|
+
PORTABLE_PROOF_SIGNER_HEADER,
|
|
5
|
+
constructVerificationMessage,
|
|
6
|
+
validateWalletAddress,
|
|
7
|
+
validateUniversalAddress,
|
|
8
|
+
signMessage,
|
|
9
|
+
NEUS_CONSTANTS
|
|
10
|
+
} from './utils.js';
|
|
11
|
+
|
|
12
|
+
const FALLBACK_PUBLIC_VERIFIER_CATALOG = {
|
|
13
|
+
'ownership-basic': { supportsDirectApi: true },
|
|
14
|
+
'ownership-social': { supportsDirectApi: false },
|
|
15
|
+
'ownership-pseudonym': { supportsDirectApi: true },
|
|
16
|
+
'ownership-dns-txt': { supportsDirectApi: true },
|
|
17
|
+
'ownership-org-oauth': { supportsDirectApi: false },
|
|
18
|
+
'contract-ownership': { supportsDirectApi: true },
|
|
19
|
+
'proof-of-human': { supportsDirectApi: false },
|
|
20
|
+
'nft-ownership': { supportsDirectApi: true },
|
|
21
|
+
'token-holding': { supportsDirectApi: true },
|
|
22
|
+
'wallet-risk': { supportsDirectApi: true },
|
|
23
|
+
'wallet-link': { supportsDirectApi: true },
|
|
24
|
+
'ai-content-moderation': { supportsDirectApi: true },
|
|
25
|
+
'agent-identity': { supportsDirectApi: true },
|
|
26
|
+
'agent-delegation': { supportsDirectApi: true }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
30
|
+
const WALLET_LINK_RELATIONSHIP_TYPES = new Set(['primary', 'personal', 'org', 'affiliate', 'agent', 'linked']);
|
|
31
|
+
|
|
32
|
+
function normalizeWalletLinkRelationshipType(value) {
|
|
33
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
34
|
+
return WALLET_LINK_RELATIONSHIP_TYPES.has(normalized) ? normalized : 'linked';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {unknown} raw
|
|
39
|
+
* @returns {string | null} Non-empty trimmed string, or null if the provider value is not a valid string identity.
|
|
40
|
+
*/
|
|
41
|
+
function normalizeBrowserSignerString(raw) {
|
|
42
|
+
if (raw === null || raw === undefined) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (typeof raw === 'string') {
|
|
46
|
+
const t = raw.trim();
|
|
47
|
+
return t.length > 0 ? t : null;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Treats SDK placeholder signatures as absent so the protocol can use session or app-link delegation. */
|
|
53
|
+
function isPlaceholderNeusSignature(signature) {
|
|
54
|
+
const s = typeof signature === 'string' ? signature.trim() : '';
|
|
55
|
+
if (!s) return true;
|
|
56
|
+
return /^0x0+$/i.test(s);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const validateVerifierData = (verifierId, data) => {
|
|
60
|
+
if (!data || typeof data !== 'object') {
|
|
61
|
+
return { valid: false, error: 'Data object is required' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
switch (verifierId) {
|
|
65
|
+
case 'ownership-basic':
|
|
66
|
+
if (!data.owner || !validateUniversalAddress(data.owner, typeof data.chain === 'string' ? data.chain : undefined)) {
|
|
67
|
+
return { valid: false, error: 'owner (universal wallet address) is required' };
|
|
68
|
+
}
|
|
69
|
+
if (data.content !== undefined && data.content !== null) {
|
|
70
|
+
if (typeof data.content !== 'string') {
|
|
71
|
+
return { valid: false, error: 'content must be a string when provided' };
|
|
72
|
+
}
|
|
73
|
+
if (data.content.length > 50000) {
|
|
74
|
+
return { valid: false, error: 'content exceeds 50KB inline limit' };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (data.contentHash !== undefined && data.contentHash !== null) {
|
|
78
|
+
if (typeof data.contentHash !== 'string' || !/^0x[a-fA-F0-9]{64}$/.test(data.contentHash)) {
|
|
79
|
+
return { valid: false, error: 'contentHash must be a 32-byte hex string (0x + 64 hex chars) when provided' };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (data.contentType !== undefined && data.contentType !== null) {
|
|
83
|
+
if (typeof data.contentType !== 'string' || data.contentType.length > 100) {
|
|
84
|
+
return { valid: false, error: 'contentType must be a string (max 100 chars) when provided' };
|
|
85
|
+
}
|
|
86
|
+
const base = String(data.contentType).split(';')[0].trim().toLowerCase();
|
|
87
|
+
if (!base || base.includes(' ') || !base.includes('/')) {
|
|
88
|
+
return { valid: false, error: 'contentType must be a valid MIME type when provided' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (data.provenance !== undefined && data.provenance !== null) {
|
|
92
|
+
if (!data.provenance || typeof data.provenance !== 'object' || Array.isArray(data.provenance)) {
|
|
93
|
+
return { valid: false, error: 'provenance must be an object when provided' };
|
|
94
|
+
}
|
|
95
|
+
const dk = data.provenance.declaredKind;
|
|
96
|
+
if (dk !== undefined && dk !== null) {
|
|
97
|
+
const allowed = ['human', 'ai', 'mixed', 'unknown'];
|
|
98
|
+
if (typeof dk !== 'string' || !allowed.includes(dk)) {
|
|
99
|
+
return { valid: false, error: `provenance.declaredKind must be one of: ${allowed.join(', ')}` };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const ai = data.provenance.aiContext;
|
|
103
|
+
if (ai !== undefined && ai !== null) {
|
|
104
|
+
if (typeof ai !== 'object' || Array.isArray(ai)) {
|
|
105
|
+
return { valid: false, error: 'provenance.aiContext must be an object when provided' };
|
|
106
|
+
}
|
|
107
|
+
if (ai.generatorType !== undefined && ai.generatorType !== null) {
|
|
108
|
+
const allowed = ['local', 'saas', 'agent'];
|
|
109
|
+
if (typeof ai.generatorType !== 'string' || !allowed.includes(ai.generatorType)) {
|
|
110
|
+
return { valid: false, error: `provenance.aiContext.generatorType must be one of: ${allowed.join(', ')}` };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (ai.provider !== undefined && ai.provider !== null) {
|
|
114
|
+
if (typeof ai.provider !== 'string' || ai.provider.length > 64) {
|
|
115
|
+
return { valid: false, error: 'provenance.aiContext.provider must be a string (max 64 chars) when provided' };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (ai.model !== undefined && ai.model !== null) {
|
|
119
|
+
if (typeof ai.model !== 'string' || ai.model.length > 128) {
|
|
120
|
+
return { valid: false, error: 'provenance.aiContext.model must be a string (max 128 chars) when provided' };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (ai.runId !== undefined && ai.runId !== null) {
|
|
124
|
+
if (typeof ai.runId !== 'string' || ai.runId.length > 128) {
|
|
125
|
+
return { valid: false, error: 'provenance.aiContext.runId must be a string (max 128 chars) when provided' };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (data.reference !== undefined) {
|
|
131
|
+
if (!data.reference || typeof data.reference !== 'object') {
|
|
132
|
+
return { valid: false, error: 'reference must be an object when provided' };
|
|
133
|
+
}
|
|
134
|
+
if (!data.reference.type || typeof data.reference.type !== 'string') {
|
|
135
|
+
return { valid: false, error: 'reference.type is required when reference is provided' };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!data.content && !data.contentHash) {
|
|
139
|
+
if (!data.reference || typeof data.reference !== 'object') {
|
|
140
|
+
return { valid: false, error: 'reference is required when neither content nor contentHash is provided' };
|
|
141
|
+
}
|
|
142
|
+
if (!data.reference.id || typeof data.reference.id !== 'string') {
|
|
143
|
+
return { valid: false, error: 'reference.id is required when neither content nor contentHash is provided' };
|
|
144
|
+
}
|
|
145
|
+
if (!data.reference.type || typeof data.reference.type !== 'string') {
|
|
146
|
+
return { valid: false, error: 'reference.type is required when neither content nor contentHash is provided' };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case 'nft-ownership':
|
|
151
|
+
if (
|
|
152
|
+
!data.contractAddress ||
|
|
153
|
+
data.tokenId === null ||
|
|
154
|
+
data.tokenId === undefined ||
|
|
155
|
+
typeof data.chainId !== 'number'
|
|
156
|
+
) {
|
|
157
|
+
return { valid: false, error: 'contractAddress, tokenId, and chainId are required' };
|
|
158
|
+
}
|
|
159
|
+
if (data.tokenType !== undefined && data.tokenType !== null) {
|
|
160
|
+
const tt = String(data.tokenType).toLowerCase();
|
|
161
|
+
if (tt !== 'erc721' && tt !== 'erc1155') {
|
|
162
|
+
return { valid: false, error: 'tokenType must be one of: erc721, erc1155' };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
|
|
166
|
+
return { valid: false, error: 'blockNumber must be an integer when provided' };
|
|
167
|
+
}
|
|
168
|
+
if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
|
|
169
|
+
return { valid: false, error: 'Invalid ownerAddress' };
|
|
170
|
+
}
|
|
171
|
+
if (!validateWalletAddress(data.contractAddress)) {
|
|
172
|
+
return { valid: false, error: 'Invalid contractAddress' };
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
case 'token-holding':
|
|
176
|
+
if (
|
|
177
|
+
!data.contractAddress ||
|
|
178
|
+
data.minBalance === null ||
|
|
179
|
+
data.minBalance === undefined ||
|
|
180
|
+
typeof data.chainId !== 'number'
|
|
181
|
+
) {
|
|
182
|
+
return { valid: false, error: 'contractAddress, minBalance, and chainId are required' };
|
|
183
|
+
}
|
|
184
|
+
if (data.blockNumber !== undefined && data.blockNumber !== null && !Number.isInteger(data.blockNumber)) {
|
|
185
|
+
return { valid: false, error: 'blockNumber must be an integer when provided' };
|
|
186
|
+
}
|
|
187
|
+
if (data.ownerAddress && !validateWalletAddress(data.ownerAddress)) {
|
|
188
|
+
return { valid: false, error: 'Invalid ownerAddress' };
|
|
189
|
+
}
|
|
190
|
+
if (!validateWalletAddress(data.contractAddress)) {
|
|
191
|
+
return { valid: false, error: 'Invalid contractAddress' };
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
case 'ownership-dns-txt':
|
|
195
|
+
if (!data.domain || typeof data.domain !== 'string') {
|
|
196
|
+
return { valid: false, error: 'domain is required' };
|
|
197
|
+
}
|
|
198
|
+
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
199
|
+
return { valid: false, error: 'Invalid walletAddress' };
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
case 'wallet-link':
|
|
203
|
+
if (!data.primaryWalletAddress || !validateUniversalAddress(data.primaryWalletAddress, data.chain)) {
|
|
204
|
+
return { valid: false, error: 'primaryWalletAddress is required' };
|
|
205
|
+
}
|
|
206
|
+
if (!data.secondaryWalletAddress || !validateUniversalAddress(data.secondaryWalletAddress, data.chain)) {
|
|
207
|
+
return { valid: false, error: 'secondaryWalletAddress is required' };
|
|
208
|
+
}
|
|
209
|
+
if (!data.signature || typeof data.signature !== 'string') {
|
|
210
|
+
return { valid: false, error: 'signature is required (signed by secondary wallet)' };
|
|
211
|
+
}
|
|
212
|
+
if (typeof data.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
|
|
213
|
+
return { valid: false, error: 'chain is required (namespace:reference)' };
|
|
214
|
+
}
|
|
215
|
+
if (typeof data.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
|
|
216
|
+
return { valid: false, error: 'signatureMethod is required' };
|
|
217
|
+
}
|
|
218
|
+
if (typeof data.signedTimestamp !== 'number') {
|
|
219
|
+
return { valid: false, error: 'signedTimestamp is required' };
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
case 'contract-ownership':
|
|
223
|
+
if (!data.contractAddress || !validateWalletAddress(data.contractAddress)) {
|
|
224
|
+
return { valid: false, error: 'contractAddress is required' };
|
|
225
|
+
}
|
|
226
|
+
if (data.walletAddress && !validateWalletAddress(data.walletAddress)) {
|
|
227
|
+
return { valid: false, error: 'Invalid walletAddress' };
|
|
228
|
+
}
|
|
229
|
+
if (typeof data.chainId !== 'number') {
|
|
230
|
+
return { valid: false, error: 'chainId is required' };
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
case 'agent-identity':
|
|
234
|
+
if (!data.agentId || typeof data.agentId !== 'string' || data.agentId.length < 1 || data.agentId.length > 128) {
|
|
235
|
+
return { valid: false, error: 'agentId is required (1-128 chars)' };
|
|
236
|
+
}
|
|
237
|
+
if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
|
|
238
|
+
return { valid: false, error: 'agentWallet is required' };
|
|
239
|
+
}
|
|
240
|
+
if (data.agentType && !['ai', 'bot', 'service', 'automation', 'agent'].includes(data.agentType)) {
|
|
241
|
+
return { valid: false, error: 'agentType must be one of: ai, bot, service, automation, agent' };
|
|
242
|
+
}
|
|
243
|
+
if (data.defaultRuntime && typeof data.defaultRuntime === 'object') {
|
|
244
|
+
if (data.defaultRuntime.provider && typeof data.defaultRuntime.provider === 'string' && data.defaultRuntime.provider.length > 64) {
|
|
245
|
+
return { valid: false, error: 'defaultRuntime.provider must be 64 chars or less' };
|
|
246
|
+
}
|
|
247
|
+
if (data.defaultRuntime.model && typeof data.defaultRuntime.model === 'string' && data.defaultRuntime.model.length > 128) {
|
|
248
|
+
return { valid: false, error: 'defaultRuntime.model must be 128 chars or less' };
|
|
249
|
+
}
|
|
250
|
+
if (data.defaultRuntime.mode && typeof data.defaultRuntime.mode === 'string' && data.defaultRuntime.mode.length > 64) {
|
|
251
|
+
return { valid: false, error: 'defaultRuntime.mode must be 64 chars or less' };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
case 'agent-delegation':
|
|
256
|
+
if (!data.controllerWallet || !validateWalletAddress(data.controllerWallet)) {
|
|
257
|
+
return { valid: false, error: 'controllerWallet is required' };
|
|
258
|
+
}
|
|
259
|
+
if (!data.agentWallet || !validateWalletAddress(data.agentWallet)) {
|
|
260
|
+
return { valid: false, error: 'agentWallet is required' };
|
|
261
|
+
}
|
|
262
|
+
if (data.scope && (typeof data.scope !== 'string' || data.scope.length > 128)) {
|
|
263
|
+
return { valid: false, error: 'scope must be a string (max 128 chars)' };
|
|
264
|
+
}
|
|
265
|
+
if (data.expiresAt && (typeof data.expiresAt !== 'number' || data.expiresAt < Date.now())) {
|
|
266
|
+
return { valid: false, error: 'expiresAt must be a future timestamp' };
|
|
267
|
+
}
|
|
268
|
+
if (data.model && typeof data.model === 'string' && data.model.length > 128) {
|
|
269
|
+
return { valid: false, error: 'model must be 128 chars or less' };
|
|
270
|
+
}
|
|
271
|
+
if (data.provider && typeof data.provider === 'string' && data.provider.length > 64) {
|
|
272
|
+
return { valid: false, error: 'provider must be 64 chars or less' };
|
|
273
|
+
}
|
|
274
|
+
if (data.allowedActions && Array.isArray(data.allowedActions) && data.allowedActions.length > 32) {
|
|
275
|
+
return { valid: false, error: 'allowedActions must have 32 items or less' };
|
|
276
|
+
}
|
|
277
|
+
if (data.deniedActions && Array.isArray(data.deniedActions) && data.deniedActions.length > 32) {
|
|
278
|
+
return { valid: false, error: 'deniedActions must have 32 items or less' };
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
case 'ai-content-moderation':
|
|
282
|
+
if (!data.content || typeof data.content !== 'string') {
|
|
283
|
+
return { valid: false, error: 'content is required' };
|
|
284
|
+
}
|
|
285
|
+
if (!data.contentType || typeof data.contentType !== 'string') {
|
|
286
|
+
return { valid: false, error: 'contentType (MIME type) is required' };
|
|
287
|
+
}
|
|
288
|
+
{
|
|
289
|
+
const contentType = String(data.contentType).split(';')[0].trim().toLowerCase();
|
|
290
|
+
const validTypes = [
|
|
291
|
+
'image/jpeg',
|
|
292
|
+
'image/png',
|
|
293
|
+
'image/webp',
|
|
294
|
+
'image/gif',
|
|
295
|
+
'text/plain',
|
|
296
|
+
'text/markdown',
|
|
297
|
+
'text/x-markdown',
|
|
298
|
+
'application/json',
|
|
299
|
+
'application/xml'
|
|
300
|
+
];
|
|
301
|
+
if (!validTypes.includes(contentType)) {
|
|
302
|
+
return { valid: false, error: `contentType must be one of: ${validTypes.join(', ')}` };
|
|
303
|
+
}
|
|
304
|
+
const isTextual = contentType.startsWith('text/') || contentType.includes('markdown');
|
|
305
|
+
if (isTextual) {
|
|
306
|
+
try {
|
|
307
|
+
const maxBytes = 50 * 1024;
|
|
308
|
+
const bytes = (typeof TextEncoder !== 'undefined')
|
|
309
|
+
? new TextEncoder().encode(data.content).length
|
|
310
|
+
: String(data.content).length;
|
|
311
|
+
if (bytes > maxBytes) {
|
|
312
|
+
return {
|
|
313
|
+
valid: false,
|
|
314
|
+
error: `content exceeds ${maxBytes} bytes limit for ai-content-moderation verifier (text)`
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
void 0;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (data.content.length > 13653334) {
|
|
323
|
+
return { valid: false, error: 'content exceeds 10MB limit' };
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
case 'ownership-pseudonym':
|
|
327
|
+
if (!data.pseudonymId || typeof data.pseudonymId !== 'string') {
|
|
328
|
+
return { valid: false, error: 'pseudonymId is required' };
|
|
329
|
+
}
|
|
330
|
+
if (!/^[a-z0-9._-]{3,64}$/.test(data.pseudonymId.trim().toLowerCase())) {
|
|
331
|
+
return { valid: false, error: 'pseudonymId must be 3-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
|
|
332
|
+
}
|
|
333
|
+
if (data.namespace && typeof data.namespace === 'string') {
|
|
334
|
+
if (!/^[a-z0-9._-]{1,64}$/.test(data.namespace.trim().toLowerCase())) {
|
|
335
|
+
return { valid: false, error: 'namespace must be 1-64 characters, lowercase alphanumeric with dots, underscores, or hyphens' };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
case 'wallet-risk':
|
|
340
|
+
if (data.walletAddress && !validateUniversalAddress(data.walletAddress, data.chain)) {
|
|
341
|
+
return { valid: false, error: 'Invalid walletAddress' };
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { valid: true };
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
export class NeusClient {
|
|
350
|
+
constructor(config = {}) {
|
|
351
|
+
this.config = {
|
|
352
|
+
timeout: 30000,
|
|
353
|
+
enableLogging: false,
|
|
354
|
+
...config
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
this.baseUrl = this.config.apiUrl || 'https://api.neus.network';
|
|
358
|
+
try {
|
|
359
|
+
const url = new URL(this.baseUrl);
|
|
360
|
+
if (url.hostname.endsWith('neus.network') && url.protocol === 'http:') {
|
|
361
|
+
url.protocol = 'https:';
|
|
362
|
+
}
|
|
363
|
+
this.baseUrl = url.toString().replace(/\/$/, '');
|
|
364
|
+
} catch {
|
|
365
|
+
void 0;
|
|
366
|
+
}
|
|
367
|
+
this.config.apiUrl = this.baseUrl;
|
|
368
|
+
|
|
369
|
+
this.defaultHeaders = {
|
|
370
|
+
'Content-Type': 'application/json',
|
|
371
|
+
'Accept': 'application/json',
|
|
372
|
+
'X-Neus-Sdk': 'js'
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
if (typeof this.config.apiKey === 'string' && this.config.apiKey.trim().length > 0) {
|
|
376
|
+
this.defaultHeaders['Authorization'] = `Bearer ${this.config.apiKey.trim()}`;
|
|
377
|
+
}
|
|
378
|
+
if (typeof this.config.appId === 'string' && this.config.appId.trim().length > 0) {
|
|
379
|
+
this.defaultHeaders['X-Neus-App'] = this.config.appId.trim();
|
|
380
|
+
}
|
|
381
|
+
if (typeof this.config.paymentSignature === 'string' && this.config.paymentSignature.trim().length > 0) {
|
|
382
|
+
this.defaultHeaders['PAYMENT-SIGNATURE'] = this.config.paymentSignature.trim();
|
|
383
|
+
}
|
|
384
|
+
if (this.config.extraHeaders && typeof this.config.extraHeaders === 'object') {
|
|
385
|
+
for (const [k, v] of Object.entries(this.config.extraHeaders)) {
|
|
386
|
+
if (!k || v === undefined || v === null) continue;
|
|
387
|
+
const key = String(k).trim();
|
|
388
|
+
const value = String(v).trim();
|
|
389
|
+
if (!key || !value) continue;
|
|
390
|
+
this.defaultHeaders[key] = value;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
if (typeof window !== 'undefined' && window.location && window.location.origin) {
|
|
395
|
+
this.defaultHeaders['X-Client-Origin'] = window.location.origin;
|
|
396
|
+
}
|
|
397
|
+
} catch {
|
|
398
|
+
void 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** @type {{ token: string, expMs: number, key: string } | null} */
|
|
402
|
+
this._sponsorGrantCache = null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
_getBillingWallet() {
|
|
406
|
+
const raw =
|
|
407
|
+
this.config.billingWallet ||
|
|
408
|
+
this.config.sponsorOrgWallet ||
|
|
409
|
+
this.config.orgWallet ||
|
|
410
|
+
null;
|
|
411
|
+
if (typeof raw !== 'string') return null;
|
|
412
|
+
const trimmed = raw.trim().toLowerCase();
|
|
413
|
+
return /^0x[a-f0-9]{40}$/.test(trimmed) ? trimmed : null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
_resolveIntegratorOrigin() {
|
|
417
|
+
if (typeof this.config.appOrigin === 'string' && this.config.appOrigin.trim()) {
|
|
418
|
+
return this.config.appOrigin.trim();
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
if (typeof window !== 'undefined' && window.location?.origin) {
|
|
422
|
+
return window.location.origin;
|
|
423
|
+
}
|
|
424
|
+
} catch {
|
|
425
|
+
void 0;
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async _resolveSponsorGrantHeaders(verifierIds = []) {
|
|
431
|
+
const appId = typeof this.config.appId === 'string' ? this.config.appId.trim() : '';
|
|
432
|
+
const orgWallet = this._getBillingWallet();
|
|
433
|
+
if (!appId || !orgWallet) {
|
|
434
|
+
return {};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const normalizedVerifierIds = Array.isArray(verifierIds)
|
|
438
|
+
? verifierIds.map((v) => String(v || '').trim()).filter(Boolean).slice(0, 25)
|
|
439
|
+
: [];
|
|
440
|
+
const cacheKey = `${appId}:${orgWallet}:${normalizedVerifierIds.join(',')}`;
|
|
441
|
+
const now = Date.now();
|
|
442
|
+
if (
|
|
443
|
+
this._sponsorGrantCache &&
|
|
444
|
+
this._sponsorGrantCache.key === cacheKey &&
|
|
445
|
+
this._sponsorGrantCache.expMs > now + 30_000
|
|
446
|
+
) {
|
|
447
|
+
return { 'X-Sponsor-Grant': this._sponsorGrantCache.token };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const origin = this._resolveIntegratorOrigin();
|
|
451
|
+
const grant = await fetchSponsorGrant({
|
|
452
|
+
apiUrl: this.baseUrl,
|
|
453
|
+
appId,
|
|
454
|
+
orgWallet,
|
|
455
|
+
verifierIds: normalizedVerifierIds,
|
|
456
|
+
origin
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const expSeconds = Number(grant?.exp);
|
|
460
|
+
const expMs = Number.isFinite(expSeconds) && expSeconds > 0
|
|
461
|
+
? expSeconds * 1000
|
|
462
|
+
: now + 15 * 60 * 1000;
|
|
463
|
+
|
|
464
|
+
this._sponsorGrantCache = {
|
|
465
|
+
key: cacheKey,
|
|
466
|
+
token: grant.sponsorGrant,
|
|
467
|
+
expMs
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
return { 'X-Sponsor-Grant': grant.sponsorGrant };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
_getHubChainId() {
|
|
474
|
+
const configured = Number(this.config?.hubChainId);
|
|
475
|
+
if (Number.isFinite(configured) && configured > 0) return Math.floor(configured);
|
|
476
|
+
return NEUS_CONSTANTS.HUB_CHAIN_ID;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
_normalizeIdentity(value) {
|
|
480
|
+
let raw = String(value || '').trim();
|
|
481
|
+
if (!raw) return '';
|
|
482
|
+
const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
|
|
483
|
+
if (didMatch && didMatch[3]) {
|
|
484
|
+
raw = String(didMatch[3]).trim();
|
|
485
|
+
}
|
|
486
|
+
return EVM_ADDRESS_RE.test(raw) ? raw.toLowerCase() : raw;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
_inferChainForAddress(address, explicitChain) {
|
|
490
|
+
if (typeof explicitChain === 'string' && explicitChain.includes(':')) return explicitChain.trim();
|
|
491
|
+
const raw = String(address || '').trim();
|
|
492
|
+
const didMatch = raw.match(/^did:pkh:([^:]+):([^:]+):(.+)$/i);
|
|
493
|
+
if (didMatch && didMatch[1] && didMatch[2]) {
|
|
494
|
+
return `${didMatch[1]}:${didMatch[2]}`;
|
|
495
|
+
}
|
|
496
|
+
if (EVM_ADDRESS_RE.test(raw)) {
|
|
497
|
+
return `eip155:${this._getHubChainId()}`;
|
|
498
|
+
}
|
|
499
|
+
return 'solana:mainnet';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async _resolveWalletSigner(wallet) {
|
|
503
|
+
if (!wallet) {
|
|
504
|
+
throw new ConfigurationError('No wallet provider available');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (wallet.address !== null && wallet.address !== undefined) {
|
|
508
|
+
const s = normalizeBrowserSignerString(
|
|
509
|
+
typeof wallet.address === 'string' ? wallet.address : null
|
|
510
|
+
);
|
|
511
|
+
if (s) {
|
|
512
|
+
return { signerWalletAddress: s, provider: wallet };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (wallet.publicKey && typeof wallet.publicKey.toBase58 === 'function') {
|
|
516
|
+
const b58 = wallet.publicKey.toBase58();
|
|
517
|
+
const s = normalizeBrowserSignerString(typeof b58 === 'string' ? b58 : null);
|
|
518
|
+
if (s) {
|
|
519
|
+
return { signerWalletAddress: s, provider: wallet };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (typeof wallet.getAddress === 'function') {
|
|
523
|
+
const got = await wallet.getAddress().catch(() => null);
|
|
524
|
+
const s = normalizeBrowserSignerString(
|
|
525
|
+
got === null || got === undefined ? null : (typeof got === 'string' ? got : null)
|
|
526
|
+
);
|
|
527
|
+
if (s) {
|
|
528
|
+
return { signerWalletAddress: s, provider: wallet };
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (wallet.selectedAddress || wallet.request) {
|
|
532
|
+
const provider = wallet;
|
|
533
|
+
if (wallet.selectedAddress) {
|
|
534
|
+
const s = normalizeBrowserSignerString(
|
|
535
|
+
typeof wallet.selectedAddress === 'string' ? wallet.selectedAddress : null
|
|
536
|
+
);
|
|
537
|
+
if (s) {
|
|
538
|
+
return { signerWalletAddress: s, provider };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const accounts = await provider.request({ method: 'eth_accounts' });
|
|
542
|
+
if (!accounts || accounts.length === 0) {
|
|
543
|
+
throw new ConfigurationError('No wallet accounts available');
|
|
544
|
+
}
|
|
545
|
+
const s = normalizeBrowserSignerString(accounts[0]);
|
|
546
|
+
if (!s) {
|
|
547
|
+
throw new ConfigurationError('No wallet accounts available');
|
|
548
|
+
}
|
|
549
|
+
return { signerWalletAddress: s, provider };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
throw new ConfigurationError('Invalid wallet provider');
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
_getDefaultBrowserWallet() {
|
|
556
|
+
if (typeof window === 'undefined') return null;
|
|
557
|
+
// Legacy convenience fallback only. Non-EVM wallets must be passed explicitly
|
|
558
|
+
// with CAIP-2 chain context so the SDK does not route them through EVM RPC.
|
|
559
|
+
return window.ethereum || null;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async _buildPrivateGateAuth({ address, wallet, chain, signatureMethod } = {}) {
|
|
563
|
+
const providerWallet = wallet || this._getDefaultBrowserWallet();
|
|
564
|
+
const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
|
|
565
|
+
if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
|
|
566
|
+
throw new ConfigurationError('No wallet accounts available');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
|
|
570
|
+
const normalizedAddress = this._normalizeIdentity(address);
|
|
571
|
+
if (!normalizedSigner || normalizedSigner !== normalizedAddress) {
|
|
572
|
+
throw new ValidationError('wallet must match address when includePrivate=true');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
|
|
576
|
+
const resolvedChain = this._inferChainForAddress(normalizedSigner, chain);
|
|
577
|
+
const resolvedSignatureMethod = (typeof signatureMethod === 'string' && signatureMethod.trim())
|
|
578
|
+
? signatureMethod.trim()
|
|
579
|
+
: (signerIsEvm ? 'eip191' : 'ed25519');
|
|
580
|
+
|
|
581
|
+
const signedTimestamp = Date.now();
|
|
582
|
+
const message = constructVerificationMessage({
|
|
583
|
+
walletAddress: signerWalletAddress,
|
|
584
|
+
signedTimestamp,
|
|
585
|
+
data: { action: 'gate_check_private_proofs', walletAddress: normalizedAddress },
|
|
586
|
+
verifierIds: ['ownership-basic'],
|
|
587
|
+
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain: resolvedChain })
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
let signature;
|
|
591
|
+
try {
|
|
592
|
+
signature = await signMessage({
|
|
593
|
+
provider,
|
|
594
|
+
message,
|
|
595
|
+
walletAddress: signerWalletAddress,
|
|
596
|
+
...(signerIsEvm ? {} : { chain: resolvedChain })
|
|
597
|
+
});
|
|
598
|
+
} catch (error) {
|
|
599
|
+
if (error.code === 4001) {
|
|
600
|
+
throw new ValidationError('User rejected signature request');
|
|
601
|
+
}
|
|
602
|
+
throw new ValidationError(`Failed to sign message: ${error.message}`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
walletAddress: signerWalletAddress,
|
|
607
|
+
signature,
|
|
608
|
+
signedTimestamp,
|
|
609
|
+
...(signerIsEvm ? {} : { chain: resolvedChain, signatureMethod: resolvedSignatureMethod })
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async createGatePrivateAuth(params = {}) {
|
|
614
|
+
const address = (params.address || '').toString();
|
|
615
|
+
if (!validateUniversalAddress(address, params.chain)) {
|
|
616
|
+
throw new ValidationError('Valid address is required');
|
|
617
|
+
}
|
|
618
|
+
return this._buildPrivateGateAuth({
|
|
619
|
+
address,
|
|
620
|
+
wallet: params.wallet,
|
|
621
|
+
chain: params.chain,
|
|
622
|
+
signatureMethod: params.signatureMethod
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async createWalletLinkData(params = {}) {
|
|
627
|
+
const normalizedPrimary = this._normalizeIdentity(params.primaryWalletAddress);
|
|
628
|
+
const normalizedSecondary = this._normalizeIdentity(params.secondaryWalletAddress);
|
|
629
|
+
|
|
630
|
+
if (!EVM_ADDRESS_RE.test(normalizedPrimary)) {
|
|
631
|
+
throw new ValidationError('wallet-link requires a valid primaryWalletAddress');
|
|
632
|
+
}
|
|
633
|
+
if (!EVM_ADDRESS_RE.test(normalizedSecondary)) {
|
|
634
|
+
throw new ValidationError('wallet-link requires a valid secondaryWalletAddress');
|
|
635
|
+
}
|
|
636
|
+
if (normalizedPrimary === normalizedSecondary) {
|
|
637
|
+
throw new ValidationError('wallet-link secondaryWalletAddress must differ from primaryWalletAddress');
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const providerWallet = params.wallet || this._getDefaultBrowserWallet();
|
|
641
|
+
const { signerWalletAddress, provider } = await this._resolveWalletSigner(providerWallet);
|
|
642
|
+
const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
|
|
643
|
+
if (!EVM_ADDRESS_RE.test(normalizedSigner)) {
|
|
644
|
+
throw new ValidationError('wallet-link requires an EVM wallet/provider for the secondary wallet');
|
|
645
|
+
}
|
|
646
|
+
if (normalizedSigner !== normalizedSecondary) {
|
|
647
|
+
throw new ValidationError('wallet-link wallet/provider must be connected to secondaryWalletAddress');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const resolvedChain = (() => {
|
|
651
|
+
const raw = String(params.chain || '').trim();
|
|
652
|
+
if (!raw) return `eip155:${this._getHubChainId()}`;
|
|
653
|
+
if (!/^eip155:\d+$/.test(raw)) {
|
|
654
|
+
throw new ValidationError('wallet-link requires chain in the form eip155:<chainId>');
|
|
655
|
+
}
|
|
656
|
+
return raw;
|
|
657
|
+
})();
|
|
658
|
+
const signedTimestamp = Number.isFinite(Number(params.signedTimestamp)) && Number(params.signedTimestamp) > 0
|
|
659
|
+
? Number(params.signedTimestamp)
|
|
660
|
+
: Date.now();
|
|
661
|
+
const linkData = {
|
|
662
|
+
primaryAccountId: `${resolvedChain}:${normalizedPrimary}`,
|
|
663
|
+
secondaryAccountId: `${resolvedChain}:${normalizedSecondary}`,
|
|
664
|
+
primaryWalletAddress: normalizedPrimary,
|
|
665
|
+
secondaryWalletAddress: normalizedSecondary
|
|
666
|
+
};
|
|
667
|
+
const message = constructVerificationMessage({
|
|
668
|
+
walletAddress: normalizedSecondary,
|
|
669
|
+
signedTimestamp,
|
|
670
|
+
data: linkData,
|
|
671
|
+
verifierIds: ['wallet-link'],
|
|
672
|
+
chain: resolvedChain
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
let signature;
|
|
676
|
+
try {
|
|
677
|
+
signature = await signMessage({
|
|
678
|
+
provider,
|
|
679
|
+
message,
|
|
680
|
+
walletAddress: signerWalletAddress
|
|
681
|
+
});
|
|
682
|
+
} catch (error) {
|
|
683
|
+
if (error?.code === 4001) {
|
|
684
|
+
throw new ValidationError('User rejected wallet-link signature request');
|
|
685
|
+
}
|
|
686
|
+
throw new ValidationError(`Failed to sign wallet-link message: ${error?.message || String(error)}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const label = String(params.label || '').trim().slice(0, 64);
|
|
690
|
+
return {
|
|
691
|
+
primaryWalletAddress: normalizedPrimary,
|
|
692
|
+
secondaryWalletAddress: normalizedSecondary,
|
|
693
|
+
signature,
|
|
694
|
+
chain: resolvedChain,
|
|
695
|
+
signatureMethod: 'eip191',
|
|
696
|
+
signedTimestamp,
|
|
697
|
+
relationshipType: normalizeWalletLinkRelationshipType(params.relationshipType),
|
|
698
|
+
...(label ? { label } : {})
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
async verifyFromApp(params) {
|
|
703
|
+
if (!params || typeof params !== 'object') {
|
|
704
|
+
throw new ValidationError('verifyFromApp requires a params object');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const {
|
|
708
|
+
user,
|
|
709
|
+
verifier = 'ownership-basic',
|
|
710
|
+
content,
|
|
711
|
+
data: explicitData = null,
|
|
712
|
+
options = {}
|
|
713
|
+
} = params;
|
|
714
|
+
|
|
715
|
+
if (!user || typeof user !== 'object') {
|
|
716
|
+
throw new ValidationError('verifyFromApp requires user object');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const walletAddress =
|
|
720
|
+
user.walletAddress ||
|
|
721
|
+
user.address ||
|
|
722
|
+
user.identity;
|
|
723
|
+
if (!walletAddress || typeof walletAddress !== 'string') {
|
|
724
|
+
throw new ValidationError('verifyFromApp requires user.walletAddress');
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const delegationQHash =
|
|
728
|
+
this.config.appLinkQHash ||
|
|
729
|
+
(typeof process !== 'undefined' && process.env && process.env.NEUS_APP_LINK_QHASH) ||
|
|
730
|
+
null;
|
|
731
|
+
|
|
732
|
+
let data;
|
|
733
|
+
if (explicitData && typeof explicitData === 'object') {
|
|
734
|
+
data = { owner: walletAddress, ...explicitData };
|
|
735
|
+
} else if (content && typeof content === 'object') {
|
|
736
|
+
data = {
|
|
737
|
+
owner: walletAddress,
|
|
738
|
+
content: JSON.stringify(content),
|
|
739
|
+
contentType: typeof content.contentType === 'string' ? content.contentType : 'application/json',
|
|
740
|
+
reference: content.reference || { type: 'other' }
|
|
741
|
+
};
|
|
742
|
+
} else if (typeof content === 'string') {
|
|
743
|
+
data = {
|
|
744
|
+
owner: walletAddress,
|
|
745
|
+
content,
|
|
746
|
+
reference: { type: 'other' }
|
|
747
|
+
};
|
|
748
|
+
} else {
|
|
749
|
+
data = { owner: walletAddress, reference: { type: 'other' } };
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const signedTimestamp = Date.now();
|
|
753
|
+
const verifierIds = [verifier];
|
|
754
|
+
|
|
755
|
+
return this.verify({
|
|
756
|
+
verifierIds,
|
|
757
|
+
data,
|
|
758
|
+
walletAddress,
|
|
759
|
+
signedTimestamp,
|
|
760
|
+
...(delegationQHash ? { delegationQHash } : {}),
|
|
761
|
+
options
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async verify(params) {
|
|
766
|
+
if (
|
|
767
|
+
(isPlaceholderNeusSignature(params?.signature) || !params?.signature || !params?.walletAddress) &&
|
|
768
|
+
(params?.verifier || params?.content || params?.data)
|
|
769
|
+
) {
|
|
770
|
+
const { content, verifier = 'ownership-basic', data = null, wallet = null, options = {} } = params;
|
|
771
|
+
|
|
772
|
+
if (verifier === 'ownership-basic' && !data && (!content || typeof content !== 'string')) {
|
|
773
|
+
throw new ValidationError('content is required and must be a string (or use data param with owner + reference)');
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
let verifierCatalog = FALLBACK_PUBLIC_VERIFIER_CATALOG;
|
|
777
|
+
try {
|
|
778
|
+
const discovered = await this.getVerifierCatalog();
|
|
779
|
+
if (discovered && discovered.metadata && Object.keys(discovered.metadata).length > 0) {
|
|
780
|
+
verifierCatalog = discovered.metadata;
|
|
781
|
+
}
|
|
782
|
+
} catch {
|
|
783
|
+
void 0;
|
|
784
|
+
}
|
|
785
|
+
const validVerifiers = Object.keys(verifierCatalog);
|
|
786
|
+
if (!validVerifiers.includes(verifier)) {
|
|
787
|
+
throw new ValidationError(`Invalid verifier '${verifier}'. Must be one of: ${validVerifiers.join(', ')}.`);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (verifierCatalog?.[verifier]?.supportsDirectApi === false) {
|
|
791
|
+
const hostedCheckoutUrl = options?.hostedCheckoutUrl || 'https://neus.network/verify';
|
|
792
|
+
throw new ValidationError(
|
|
793
|
+
`${verifier} requires hosted interactive checkout. Use VerifyGate or redirect to ${hostedCheckoutUrl}.`
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const requiresDataParam = [
|
|
798
|
+
'ownership-dns-txt',
|
|
799
|
+
'wallet-link',
|
|
800
|
+
'contract-ownership',
|
|
801
|
+
'ownership-pseudonym',
|
|
802
|
+
'wallet-risk',
|
|
803
|
+
'agent-identity',
|
|
804
|
+
'agent-delegation',
|
|
805
|
+
'ai-content-moderation'
|
|
806
|
+
];
|
|
807
|
+
if (requiresDataParam.includes(verifier)) {
|
|
808
|
+
if (!data || typeof data !== 'object') {
|
|
809
|
+
throw new ValidationError(`${verifier} requires explicit data parameter. Cannot use auto-path.`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
let walletAddress, provider;
|
|
814
|
+
if (wallet) {
|
|
815
|
+
walletAddress =
|
|
816
|
+
wallet.address ||
|
|
817
|
+
wallet.selectedAddress ||
|
|
818
|
+
wallet.walletAddress ||
|
|
819
|
+
(typeof wallet.getAddress === 'function' ? await wallet.getAddress().catch(() => null) : null);
|
|
820
|
+
provider = wallet.provider || wallet;
|
|
821
|
+
if (!walletAddress && provider && typeof provider.request === 'function') {
|
|
822
|
+
let accounts = await provider.request({ method: 'eth_accounts' });
|
|
823
|
+
walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
|
|
824
|
+
if (!walletAddress) {
|
|
825
|
+
await provider.request({ method: 'eth_requestAccounts' });
|
|
826
|
+
accounts = await provider.request({ method: 'eth_accounts' });
|
|
827
|
+
walletAddress = Array.isArray(accounts) && accounts.length > 0 ? accounts[0] : null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
} else {
|
|
831
|
+
if (typeof window === 'undefined' || !window.ethereum) {
|
|
832
|
+
throw new ConfigurationError('No EVM browser wallet detected. Provide wallet explicitly for non-EVM flows and include chain as a CAIP-2 value.');
|
|
833
|
+
}
|
|
834
|
+
await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
835
|
+
provider = window.ethereum;
|
|
836
|
+
const accounts = await provider.request({ method: 'eth_accounts' });
|
|
837
|
+
walletAddress = accounts[0];
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
{
|
|
841
|
+
const normalized = normalizeBrowserSignerString(
|
|
842
|
+
typeof walletAddress === 'string' ? walletAddress : null
|
|
843
|
+
);
|
|
844
|
+
if (!normalized) {
|
|
845
|
+
throw new ConfigurationError('No wallet accounts available. Connect a wallet to continue.');
|
|
846
|
+
}
|
|
847
|
+
walletAddress = normalized;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
let verificationData;
|
|
851
|
+
if (verifier === 'ownership-basic') {
|
|
852
|
+
if (data && typeof data === 'object') {
|
|
853
|
+
verificationData = {
|
|
854
|
+
owner: data.owner || walletAddress,
|
|
855
|
+
reference: data.reference,
|
|
856
|
+
...(data.content && { content: data.content }),
|
|
857
|
+
...(data.contentHash && { contentHash: data.contentHash }),
|
|
858
|
+
...(data.contentType && { contentType: data.contentType }),
|
|
859
|
+
...(data.provenance && { provenance: data.provenance })
|
|
860
|
+
};
|
|
861
|
+
} else {
|
|
862
|
+
verificationData = {
|
|
863
|
+
content: content,
|
|
864
|
+
owner: walletAddress,
|
|
865
|
+
reference: { type: 'other' }
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
} else if (verifier === 'token-holding') {
|
|
869
|
+
if (!data?.contractAddress) {
|
|
870
|
+
throw new ValidationError('token-holding requires contractAddress in data parameter');
|
|
871
|
+
}
|
|
872
|
+
if (data?.minBalance === null || data?.minBalance === undefined) {
|
|
873
|
+
throw new ValidationError('token-holding requires minBalance in data parameter');
|
|
874
|
+
}
|
|
875
|
+
if (typeof data?.chainId !== 'number') {
|
|
876
|
+
throw new ValidationError('token-holding requires chainId (number) in data parameter');
|
|
877
|
+
}
|
|
878
|
+
verificationData = {
|
|
879
|
+
ownerAddress: walletAddress,
|
|
880
|
+
contractAddress: data.contractAddress,
|
|
881
|
+
minBalance: data.minBalance,
|
|
882
|
+
chainId: data.chainId
|
|
883
|
+
};
|
|
884
|
+
} else if (verifier === 'nft-ownership') {
|
|
885
|
+
if (!data?.contractAddress) {
|
|
886
|
+
throw new ValidationError('nft-ownership requires contractAddress in data parameter');
|
|
887
|
+
}
|
|
888
|
+
if (data?.tokenId === null || data?.tokenId === undefined) {
|
|
889
|
+
throw new ValidationError('nft-ownership requires tokenId in data parameter');
|
|
890
|
+
}
|
|
891
|
+
if (typeof data?.chainId !== 'number') {
|
|
892
|
+
throw new ValidationError('nft-ownership requires chainId (number) in data parameter');
|
|
893
|
+
}
|
|
894
|
+
verificationData = {
|
|
895
|
+
ownerAddress: walletAddress,
|
|
896
|
+
contractAddress: data.contractAddress,
|
|
897
|
+
tokenId: data.tokenId,
|
|
898
|
+
chainId: data.chainId,
|
|
899
|
+
tokenType: data?.tokenType || 'erc721'
|
|
900
|
+
};
|
|
901
|
+
} else if (verifier === 'ownership-dns-txt') {
|
|
902
|
+
if (!data?.domain) {
|
|
903
|
+
throw new ValidationError('ownership-dns-txt requires domain in data parameter');
|
|
904
|
+
}
|
|
905
|
+
verificationData = {
|
|
906
|
+
domain: data.domain,
|
|
907
|
+
walletAddress: walletAddress
|
|
908
|
+
};
|
|
909
|
+
} else if (verifier === 'wallet-link') {
|
|
910
|
+
if (!data?.secondaryWalletAddress) {
|
|
911
|
+
throw new ValidationError('wallet-link requires secondaryWalletAddress in data parameter');
|
|
912
|
+
}
|
|
913
|
+
if (!data?.signature) {
|
|
914
|
+
throw new ValidationError('wallet-link requires signature in data parameter (signed by secondary wallet)');
|
|
915
|
+
}
|
|
916
|
+
if (typeof data?.chain !== 'string' || !/^[a-z0-9]+:[^\s]+$/.test(data.chain)) {
|
|
917
|
+
throw new ValidationError('wallet-link requires chain (namespace:reference) in data parameter');
|
|
918
|
+
}
|
|
919
|
+
if (typeof data?.signatureMethod !== 'string' || !data.signatureMethod.trim()) {
|
|
920
|
+
throw new ValidationError('wallet-link requires signatureMethod in data parameter');
|
|
921
|
+
}
|
|
922
|
+
verificationData = {
|
|
923
|
+
primaryWalletAddress: walletAddress,
|
|
924
|
+
secondaryWalletAddress: data.secondaryWalletAddress,
|
|
925
|
+
signature: data.signature,
|
|
926
|
+
chain: data.chain,
|
|
927
|
+
signatureMethod: data.signatureMethod,
|
|
928
|
+
signedTimestamp: data?.signedTimestamp || Date.now()
|
|
929
|
+
};
|
|
930
|
+
} else if (verifier === 'contract-ownership') {
|
|
931
|
+
if (!data?.contractAddress) {
|
|
932
|
+
throw new ValidationError('contract-ownership requires contractAddress in data parameter');
|
|
933
|
+
}
|
|
934
|
+
if (typeof data?.chainId !== 'number') {
|
|
935
|
+
throw new ValidationError('contract-ownership requires chainId (number) in data parameter');
|
|
936
|
+
}
|
|
937
|
+
verificationData = {
|
|
938
|
+
contractAddress: data.contractAddress,
|
|
939
|
+
walletAddress: walletAddress,
|
|
940
|
+
chainId: data.chainId,
|
|
941
|
+
...(data?.method && { method: data.method })
|
|
942
|
+
};
|
|
943
|
+
} else if (verifier === 'agent-identity') {
|
|
944
|
+
if (!data?.agentId) {
|
|
945
|
+
throw new ValidationError('agent-identity requires agentId in data parameter');
|
|
946
|
+
}
|
|
947
|
+
verificationData = {
|
|
948
|
+
agentId: data.agentId,
|
|
949
|
+
agentWallet: data?.agentWallet || walletAddress,
|
|
950
|
+
...(data?.agentChainRef && { agentChainRef: data.agentChainRef }),
|
|
951
|
+
...(data?.agentAccountId && { agentAccountId: data.agentAccountId }),
|
|
952
|
+
...(data?.agentLabel && { agentLabel: data.agentLabel }),
|
|
953
|
+
...(data?.agentType && { agentType: data.agentType }),
|
|
954
|
+
...(data?.avatar && { avatar: data.avatar }),
|
|
955
|
+
...(data?.description && { description: data.description }),
|
|
956
|
+
...(data?.defaultRuntime && { defaultRuntime: data.defaultRuntime }),
|
|
957
|
+
...(data?.capabilities && { capabilities: data.capabilities }),
|
|
958
|
+
...(data?.instructions && { instructions: data.instructions }),
|
|
959
|
+
...(data?.skills && { skills: data.skills }),
|
|
960
|
+
...(data?.services && { services: data.services })
|
|
961
|
+
};
|
|
962
|
+
} else if (verifier === 'agent-delegation') {
|
|
963
|
+
if (!data?.agentWallet) {
|
|
964
|
+
throw new ValidationError('agent-delegation requires agentWallet in data parameter');
|
|
965
|
+
}
|
|
966
|
+
verificationData = {
|
|
967
|
+
controllerWallet: data?.controllerWallet || walletAddress,
|
|
968
|
+
...(data?.controllerChainRef && { controllerChainRef: data.controllerChainRef }),
|
|
969
|
+
agentWallet: data.agentWallet,
|
|
970
|
+
...(data?.agentChainRef && { agentChainRef: data.agentChainRef }),
|
|
971
|
+
...(data?.controllerAccountId && { controllerAccountId: data.controllerAccountId }),
|
|
972
|
+
...(data?.agentAccountId && { agentAccountId: data.agentAccountId }),
|
|
973
|
+
...(data?.agentId && { agentId: data.agentId }),
|
|
974
|
+
...(data?.scope && { scope: data.scope }),
|
|
975
|
+
...(data?.permissions && { permissions: data.permissions }),
|
|
976
|
+
...(data?.maxSpend && { maxSpend: data.maxSpend }),
|
|
977
|
+
...(data?.allowedPaymentTypes && { allowedPaymentTypes: data.allowedPaymentTypes }),
|
|
978
|
+
...(data?.receiptDisclosure && { receiptDisclosure: data.receiptDisclosure }),
|
|
979
|
+
...(data?.expiresAt && { expiresAt: data.expiresAt }),
|
|
980
|
+
...(data?.instructions && { instructions: data.instructions }),
|
|
981
|
+
...(data?.skills && { skills: data.skills }),
|
|
982
|
+
...(data?.model && { model: data.model }),
|
|
983
|
+
...(data?.provider && { provider: data.provider }),
|
|
984
|
+
...(data?.runtimePolicy && { runtimePolicy: data.runtimePolicy }),
|
|
985
|
+
...(data?.allowedActions && { allowedActions: data.allowedActions }),
|
|
986
|
+
...(data?.deniedActions && { deniedActions: data.deniedActions }),
|
|
987
|
+
...(data?.approvalPolicy && { approvalPolicy: data.approvalPolicy })
|
|
988
|
+
};
|
|
989
|
+
} else if (verifier === 'ai-content-moderation') {
|
|
990
|
+
if (!data?.content) {
|
|
991
|
+
throw new ValidationError('ai-content-moderation requires content (base64) in data parameter');
|
|
992
|
+
}
|
|
993
|
+
if (!data?.contentType) {
|
|
994
|
+
throw new ValidationError('ai-content-moderation requires contentType (MIME type) in data parameter');
|
|
995
|
+
}
|
|
996
|
+
verificationData = {
|
|
997
|
+
content: data.content,
|
|
998
|
+
contentType: data.contentType,
|
|
999
|
+
...(data?.provider && { provider: data.provider })
|
|
1000
|
+
};
|
|
1001
|
+
} else if (verifier === 'ownership-pseudonym') {
|
|
1002
|
+
if (!data?.pseudonymId) {
|
|
1003
|
+
throw new ValidationError('ownership-pseudonym requires pseudonymId in data parameter');
|
|
1004
|
+
}
|
|
1005
|
+
verificationData = {
|
|
1006
|
+
pseudonymId: data.pseudonymId,
|
|
1007
|
+
...(data?.namespace && { namespace: data.namespace }),
|
|
1008
|
+
...(data?.displayName && { displayName: data.displayName }),
|
|
1009
|
+
...(data?.metadata && { metadata: data.metadata })
|
|
1010
|
+
};
|
|
1011
|
+
} else if (verifier === 'wallet-risk') {
|
|
1012
|
+
verificationData = {
|
|
1013
|
+
walletAddress: data?.walletAddress || walletAddress,
|
|
1014
|
+
...(data?.provider && { provider: data.provider }),
|
|
1015
|
+
chainId: (typeof data?.chainId === 'number' && Number.isFinite(data.chainId)) ? data.chainId : 1,
|
|
1016
|
+
...(data?.includeDetails !== undefined && { includeDetails: data.includeDetails })
|
|
1017
|
+
};
|
|
1018
|
+
} else {
|
|
1019
|
+
verificationData = data ? {
|
|
1020
|
+
content,
|
|
1021
|
+
owner: walletAddress,
|
|
1022
|
+
...data
|
|
1023
|
+
} : {
|
|
1024
|
+
content,
|
|
1025
|
+
owner: walletAddress
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
const signedTimestamp = Date.now();
|
|
1030
|
+
const verifierIds = [verifier];
|
|
1031
|
+
const message = constructVerificationMessage({
|
|
1032
|
+
walletAddress,
|
|
1033
|
+
signedTimestamp,
|
|
1034
|
+
data: verificationData,
|
|
1035
|
+
verifierIds,
|
|
1036
|
+
chainId: this._getHubChainId()
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
let signature;
|
|
1040
|
+
try {
|
|
1041
|
+
const toHexUtf8 = (s) => {
|
|
1042
|
+
try {
|
|
1043
|
+
const enc = new TextEncoder();
|
|
1044
|
+
const bytes = enc.encode(s);
|
|
1045
|
+
let hex = '0x';
|
|
1046
|
+
for (let i = 0; i < bytes.length; i++) hex += bytes[i].toString(16).padStart(2, '0');
|
|
1047
|
+
return hex;
|
|
1048
|
+
} catch {
|
|
1049
|
+
let hex = '0x';
|
|
1050
|
+
for (let i = 0; i < s.length; i++) hex += s.charCodeAt(i).toString(16).padStart(2, '0');
|
|
1051
|
+
return hex;
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
const isBaseMiniAppWallet = (() => {
|
|
1056
|
+
if (typeof window === 'undefined') return false;
|
|
1057
|
+
try {
|
|
1058
|
+
const w = window;
|
|
1059
|
+
const mini = w.mini;
|
|
1060
|
+
if (!mini) return false;
|
|
1061
|
+
const miniProvider = mini.wallet || mini.provider;
|
|
1062
|
+
if (miniProvider === provider) return true;
|
|
1063
|
+
if (w.ethereum === provider && mini) return true;
|
|
1064
|
+
} catch {
|
|
1065
|
+
void 0;
|
|
1066
|
+
}
|
|
1067
|
+
return false;
|
|
1068
|
+
})();
|
|
1069
|
+
|
|
1070
|
+
if (isBaseMiniAppWallet) {
|
|
1071
|
+
try {
|
|
1072
|
+
const hexMsg = toHexUtf8(message);
|
|
1073
|
+
signature = await provider.request({ method: 'personal_sign', params: [hexMsg, walletAddress] });
|
|
1074
|
+
} catch (e) {
|
|
1075
|
+
void e;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
if (!signature) {
|
|
1080
|
+
try {
|
|
1081
|
+
signature = await provider.request({ method: 'personal_sign', params: [message, walletAddress] });
|
|
1082
|
+
} catch (e) {
|
|
1083
|
+
const msg = String(e && (e.message || e.reason) || e || '').toLowerCase();
|
|
1084
|
+
const errCode = (e && (e.code || (e.error && e.error.code))) || null;
|
|
1085
|
+
const needsHex = /byte|bytes|invalid byte sequence|encoding|non-hex/i.test(msg);
|
|
1086
|
+
|
|
1087
|
+
const unsupportedRe =
|
|
1088
|
+
/method.*not.*supported|unsupported|not implemented|method not found|unknown method|does not support/i;
|
|
1089
|
+
const methodUnsupported = (
|
|
1090
|
+
unsupportedRe.test(msg) ||
|
|
1091
|
+
errCode === -32601 ||
|
|
1092
|
+
errCode === 4200 ||
|
|
1093
|
+
(msg.includes('personal_sign') && msg.includes('not')) ||
|
|
1094
|
+
(msg.includes('request method') && msg.includes('not supported'))
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
if (methodUnsupported) {
|
|
1098
|
+
this._log('personal_sign not supported; attempting eth_sign fallback');
|
|
1099
|
+
try {
|
|
1100
|
+
const enc = new TextEncoder();
|
|
1101
|
+
const bytes = enc.encode(message);
|
|
1102
|
+
const prefix = `\x19Ethereum Signed Message:\n${bytes.length}`;
|
|
1103
|
+
const full = new Uint8Array(prefix.length + bytes.length);
|
|
1104
|
+
for (let i = 0; i < prefix.length; i++) full[i] = prefix.charCodeAt(i);
|
|
1105
|
+
full.set(bytes, prefix.length);
|
|
1106
|
+
let payloadHex = '0x';
|
|
1107
|
+
for (let i = 0; i < full.length; i++) payloadHex += full[i].toString(16).padStart(2, '0');
|
|
1108
|
+
try {
|
|
1109
|
+
if (typeof window !== 'undefined') window.__NEUS_ALLOW_ETH_SIGN__ = true;
|
|
1110
|
+
signature = await provider.request({ method: 'eth_sign', params: [walletAddress, payloadHex], neusAllowEthSign: true });
|
|
1111
|
+
} finally {
|
|
1112
|
+
try { if (typeof window !== 'undefined') delete window.__NEUS_ALLOW_ETH_SIGN__; } catch { void 0; }
|
|
1113
|
+
}
|
|
1114
|
+
} catch (fallbackErr) {
|
|
1115
|
+
this._log('eth_sign fallback failed', { message: fallbackErr?.message || String(fallbackErr) });
|
|
1116
|
+
throw e;
|
|
1117
|
+
}
|
|
1118
|
+
} else if (needsHex) {
|
|
1119
|
+
this._log('Retrying personal_sign with hex-encoded message');
|
|
1120
|
+
const hexMsg = toHexUtf8(message);
|
|
1121
|
+
signature = await provider.request({ method: 'personal_sign', params: [hexMsg, walletAddress] });
|
|
1122
|
+
} else {
|
|
1123
|
+
throw e;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
if (error.code === 4001) {
|
|
1129
|
+
throw new ValidationError('User rejected the signature request. Signature is required to create proofs.');
|
|
1130
|
+
}
|
|
1131
|
+
throw new ValidationError(`Failed to sign verification message: ${error.message}`);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
return this.verify({
|
|
1135
|
+
verifierIds,
|
|
1136
|
+
data: verificationData,
|
|
1137
|
+
walletAddress,
|
|
1138
|
+
signature,
|
|
1139
|
+
signedTimestamp,
|
|
1140
|
+
options
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
const {
|
|
1145
|
+
verifierIds,
|
|
1146
|
+
data,
|
|
1147
|
+
walletAddress,
|
|
1148
|
+
signature: rawSignature,
|
|
1149
|
+
signedTimestamp,
|
|
1150
|
+
chainId,
|
|
1151
|
+
chain,
|
|
1152
|
+
signatureMethod,
|
|
1153
|
+
delegationQHash,
|
|
1154
|
+
options = {}
|
|
1155
|
+
} = params;
|
|
1156
|
+
|
|
1157
|
+
const signature = isPlaceholderNeusSignature(rawSignature) ? undefined : rawSignature;
|
|
1158
|
+
|
|
1159
|
+
const resolvedChainId = chainId || (chain ? null : NEUS_CONSTANTS.HUB_CHAIN_ID);
|
|
1160
|
+
|
|
1161
|
+
const normalizeVerifierId = (id) => {
|
|
1162
|
+
if (typeof id !== 'string') return id;
|
|
1163
|
+
const match = id.match(/^(.*)@\d+$/);
|
|
1164
|
+
return match ? match[1] : id;
|
|
1165
|
+
};
|
|
1166
|
+
const normalizedVerifierIds = Array.isArray(verifierIds) ? verifierIds.map(normalizeVerifierId) : [];
|
|
1167
|
+
|
|
1168
|
+
if (!normalizedVerifierIds || normalizedVerifierIds.length === 0) {
|
|
1169
|
+
throw new ValidationError('verifierIds array is required');
|
|
1170
|
+
}
|
|
1171
|
+
if (!data || typeof data !== 'object') {
|
|
1172
|
+
throw new ValidationError('data object is required');
|
|
1173
|
+
}
|
|
1174
|
+
if (!walletAddress || typeof walletAddress !== 'string') {
|
|
1175
|
+
throw new ValidationError('walletAddress is required');
|
|
1176
|
+
}
|
|
1177
|
+
const hasAppAttribution =
|
|
1178
|
+
typeof this.config.appId === 'string' && this.config.appId.trim().length > 0;
|
|
1179
|
+
if (!signature && !delegationQHash && !hasAppAttribution) {
|
|
1180
|
+
throw new ValidationError(
|
|
1181
|
+
'signature, delegationQHash, or NeusClient config appId (sent as X-Neus-App) is required'
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
if (!signedTimestamp || typeof signedTimestamp !== 'number') {
|
|
1185
|
+
throw new ValidationError('signedTimestamp is required');
|
|
1186
|
+
}
|
|
1187
|
+
if (resolvedChainId !== null && typeof resolvedChainId !== 'number') {
|
|
1188
|
+
throw new ValidationError('chainId must be a number');
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
for (const verifierId of normalizedVerifierIds) {
|
|
1192
|
+
const validation = validateVerifierData(verifierId, data);
|
|
1193
|
+
if (!validation.valid) {
|
|
1194
|
+
throw new ValidationError(`Validation failed for verifier '${verifierId}': ${validation.error}`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const optionsPayload = {
|
|
1199
|
+
...(options && typeof options === 'object' ? options : {}),
|
|
1200
|
+
targetChains: Array.isArray(options?.targetChains) ? options.targetChains : [],
|
|
1201
|
+
privacyLevel: options?.privacyLevel || 'private',
|
|
1202
|
+
publicDisplay: options?.publicDisplay || false,
|
|
1203
|
+
storeOriginalContent:
|
|
1204
|
+
typeof options?.storeOriginalContent === 'boolean' ? options.storeOriginalContent : true
|
|
1205
|
+
};
|
|
1206
|
+
if (typeof options?.enableIpfs === 'boolean') optionsPayload.enableIpfs = options.enableIpfs;
|
|
1207
|
+
// Receipts persist offchain by default; hub registry anchoring is explicit opt-in.
|
|
1208
|
+
if (options?.publishToHub === true) {
|
|
1209
|
+
optionsPayload.publishToHub = true;
|
|
1210
|
+
} else {
|
|
1211
|
+
delete optionsPayload.publishToHub;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const requestData = {
|
|
1215
|
+
verifierIds: normalizedVerifierIds,
|
|
1216
|
+
data,
|
|
1217
|
+
walletAddress,
|
|
1218
|
+
...(signature ? { signature } : {}),
|
|
1219
|
+
signedTimestamp,
|
|
1220
|
+
...(resolvedChainId !== null && { chainId: resolvedChainId }),
|
|
1221
|
+
...(chain && { chain }),
|
|
1222
|
+
...(signatureMethod && { signatureMethod }),
|
|
1223
|
+
...(delegationQHash && { delegationQHash }),
|
|
1224
|
+
options: optionsPayload
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
const sponsorHeaders = await this._resolveSponsorGrantHeaders(normalizedVerifierIds);
|
|
1228
|
+
const response = await this._makeRequest('POST', '/api/v1/verification', requestData, sponsorHeaders);
|
|
1229
|
+
|
|
1230
|
+
if (!response.success) {
|
|
1231
|
+
throw new ApiError(`Verification failed: ${response.error?.message || 'Unknown error'}`, response.error);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
return this._formatResponse(response);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
async getProof(qHash) {
|
|
1238
|
+
if (!qHash || typeof qHash !== 'string') {
|
|
1239
|
+
throw new ValidationError('qHash is required');
|
|
1240
|
+
}
|
|
1241
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`);
|
|
1242
|
+
|
|
1243
|
+
if (!response.success) {
|
|
1244
|
+
throw new ApiError(`Failed to get proof: ${response.error?.message || 'Unknown error'}`, response.error);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
return this._formatResponse(response);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
async getPrivateProof(qHash, wallet = null) {
|
|
1251
|
+
if (!qHash || typeof qHash !== 'string') {
|
|
1252
|
+
throw new ValidationError('qHash is required');
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const isPreSignedAuth = wallet &&
|
|
1256
|
+
typeof wallet === 'object' &&
|
|
1257
|
+
typeof wallet.walletAddress === 'string' &&
|
|
1258
|
+
typeof wallet.signature === 'string' &&
|
|
1259
|
+
typeof wallet.signedTimestamp === 'number';
|
|
1260
|
+
if (isPreSignedAuth) {
|
|
1261
|
+
const auth = wallet;
|
|
1262
|
+
const headers = {
|
|
1263
|
+
'x-wallet-address': String(auth.walletAddress),
|
|
1264
|
+
'x-signature': String(auth.signature),
|
|
1265
|
+
'x-signed-timestamp': String(auth.signedTimestamp),
|
|
1266
|
+
...(typeof auth.chain === 'string' && auth.chain.trim() ? { 'x-chain': auth.chain.trim() } : {}),
|
|
1267
|
+
...(typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim() ? { 'x-signature-method': auth.signatureMethod.trim() } : {})
|
|
1268
|
+
};
|
|
1269
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`, null, headers);
|
|
1270
|
+
if (!response.success) {
|
|
1271
|
+
throw new ApiError(
|
|
1272
|
+
`Failed to access private proof: ${response.error?.message || 'Unauthorized'}`,
|
|
1273
|
+
response.error
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
return this._formatResponse(response);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const providerWallet = wallet || this._getDefaultBrowserWallet();
|
|
1280
|
+
const { signerWalletAddress: walletAddress, provider } = await this._resolveWalletSigner(providerWallet);
|
|
1281
|
+
if (!walletAddress || typeof walletAddress !== 'string') {
|
|
1282
|
+
throw new ConfigurationError('No wallet accounts available');
|
|
1283
|
+
}
|
|
1284
|
+
const signerIsEvm = EVM_ADDRESS_RE.test(this._normalizeIdentity(walletAddress));
|
|
1285
|
+
const chain = this._inferChainForAddress(walletAddress);
|
|
1286
|
+
const signatureMethod = signerIsEvm ? 'eip191' : 'ed25519';
|
|
1287
|
+
|
|
1288
|
+
const signedTimestamp = Date.now();
|
|
1289
|
+
|
|
1290
|
+
const message = constructVerificationMessage({
|
|
1291
|
+
walletAddress,
|
|
1292
|
+
signedTimestamp,
|
|
1293
|
+
data: { action: 'access_private_proof', qHash: qHash },
|
|
1294
|
+
verifierIds: ['ownership-basic'],
|
|
1295
|
+
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
let signature;
|
|
1299
|
+
try {
|
|
1300
|
+
signature = await signMessage({
|
|
1301
|
+
provider,
|
|
1302
|
+
message,
|
|
1303
|
+
walletAddress,
|
|
1304
|
+
...(signerIsEvm ? {} : { chain })
|
|
1305
|
+
});
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
if (error.code === 4001) {
|
|
1308
|
+
throw new ValidationError('User rejected signature request');
|
|
1309
|
+
}
|
|
1310
|
+
throw new ValidationError(`Failed to sign message: ${error.message}`);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/${qHash}`, null, {
|
|
1314
|
+
'x-wallet-address': walletAddress,
|
|
1315
|
+
'x-signature': signature,
|
|
1316
|
+
'x-signed-timestamp': signedTimestamp.toString(),
|
|
1317
|
+
...(signerIsEvm ? {} : { 'x-chain': chain, 'x-signature-method': signatureMethod })
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
if (!response.success) {
|
|
1321
|
+
throw new ApiError(
|
|
1322
|
+
`Failed to access private proof: ${response.error?.message || 'Unauthorized'}`,
|
|
1323
|
+
response.error
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
return this._formatResponse(response);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
async isHealthy() {
|
|
1331
|
+
try {
|
|
1332
|
+
const response = await this._makeRequest('GET', '/api/v1/health');
|
|
1333
|
+
return response.success === true;
|
|
1334
|
+
} catch {
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
async getVerifiers() {
|
|
1340
|
+
const catalog = await this.getVerifierCatalog();
|
|
1341
|
+
return Array.isArray(catalog?.data) ? catalog.data : [];
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
async getVerifierCatalog() {
|
|
1345
|
+
const response = await this._makeRequest('GET', '/api/v1/verification/verifiers');
|
|
1346
|
+
if (!response.success) {
|
|
1347
|
+
throw new ApiError(`Failed to get verifiers: ${response.error?.message || 'Unknown error'}`, response.error);
|
|
1348
|
+
}
|
|
1349
|
+
return {
|
|
1350
|
+
data: Array.isArray(response.data) ? response.data : [],
|
|
1351
|
+
metadata:
|
|
1352
|
+
response.metadata && typeof response.metadata === 'object' && !Array.isArray(response.metadata)
|
|
1353
|
+
? response.metadata
|
|
1354
|
+
: {},
|
|
1355
|
+
meta:
|
|
1356
|
+
response.meta && typeof response.meta === 'object' && !Array.isArray(response.meta)
|
|
1357
|
+
? response.meta
|
|
1358
|
+
: {}
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
async pollProofStatus(qHash, options = {}) {
|
|
1363
|
+
const {
|
|
1364
|
+
interval = 5000,
|
|
1365
|
+
timeout = 120000,
|
|
1366
|
+
onProgress
|
|
1367
|
+
} = options;
|
|
1368
|
+
|
|
1369
|
+
if (!qHash || typeof qHash !== 'string') {
|
|
1370
|
+
throw new ValidationError('qHash is required');
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const startTime = Date.now();
|
|
1374
|
+
let consecutiveRateLimits = 0;
|
|
1375
|
+
|
|
1376
|
+
while (Date.now() - startTime < timeout) {
|
|
1377
|
+
try {
|
|
1378
|
+
const status = await this.getProof(qHash);
|
|
1379
|
+
consecutiveRateLimits = 0;
|
|
1380
|
+
|
|
1381
|
+
if (onProgress && typeof onProgress === 'function') {
|
|
1382
|
+
onProgress(status.data || status);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const currentStatus = status.data?.status || status.status;
|
|
1386
|
+
if (this._isTerminalStatus(currentStatus)) {
|
|
1387
|
+
this._log('Verification completed', { status: currentStatus, duration: Date.now() - startTime });
|
|
1388
|
+
return status;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
1392
|
+
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
this._log('Status poll error', error.message);
|
|
1395
|
+
if (error instanceof ValidationError) {
|
|
1396
|
+
throw error;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
let nextDelay = interval;
|
|
1400
|
+
if (error instanceof ApiError && Number(error.statusCode) === 429) {
|
|
1401
|
+
consecutiveRateLimits += 1;
|
|
1402
|
+
const exp = Math.min(6, consecutiveRateLimits);
|
|
1403
|
+
const base = Math.max(500, Number(interval) || 0);
|
|
1404
|
+
const max = 30000;
|
|
1405
|
+
const backoff = Math.min(max, base * Math.pow(2, exp));
|
|
1406
|
+
const jitter = Math.floor(backoff * (0.5 + Math.random() * 0.5));
|
|
1407
|
+
nextDelay = jitter;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
await new Promise(resolve => setTimeout(resolve, nextDelay));
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
throw new NetworkError(`Polling timeout after ${timeout}ms`, 'POLLING_TIMEOUT');
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
async detectChainId() {
|
|
1418
|
+
if (typeof window === 'undefined' || !window.ethereum) {
|
|
1419
|
+
throw new ConfigurationError('No Web3 wallet detected');
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
try {
|
|
1423
|
+
const chainId = await window.ethereum.request({ method: 'eth_chainId' });
|
|
1424
|
+
return parseInt(chainId, 16);
|
|
1425
|
+
} catch (error) {
|
|
1426
|
+
throw new NetworkError(`Failed to detect chain ID: ${error.message}`);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
async revokeOwnProof(qHash, wallet) {
|
|
1431
|
+
if (!qHash || typeof qHash !== 'string') {
|
|
1432
|
+
throw new ValidationError('qHash is required');
|
|
1433
|
+
}
|
|
1434
|
+
const providerWallet = wallet || this._getDefaultBrowserWallet();
|
|
1435
|
+
const { signerWalletAddress: address, provider } = await this._resolveWalletSigner(providerWallet);
|
|
1436
|
+
if (!address || typeof address !== 'string') {
|
|
1437
|
+
throw new ConfigurationError('No wallet accounts available');
|
|
1438
|
+
}
|
|
1439
|
+
const signerIsEvm = EVM_ADDRESS_RE.test(this._normalizeIdentity(address));
|
|
1440
|
+
const chain = this._inferChainForAddress(address);
|
|
1441
|
+
const signatureMethod = signerIsEvm ? 'eip191' : 'ed25519';
|
|
1442
|
+
const signedTimestamp = Date.now();
|
|
1443
|
+
|
|
1444
|
+
const message = constructVerificationMessage({
|
|
1445
|
+
walletAddress: address,
|
|
1446
|
+
signedTimestamp,
|
|
1447
|
+
data: { action: 'revoke_proof', qHash: qHash },
|
|
1448
|
+
verifierIds: ['ownership-basic'],
|
|
1449
|
+
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
let signature;
|
|
1453
|
+
try {
|
|
1454
|
+
signature = await signMessage({
|
|
1455
|
+
provider,
|
|
1456
|
+
message,
|
|
1457
|
+
walletAddress: address,
|
|
1458
|
+
...(signerIsEvm ? {} : { chain })
|
|
1459
|
+
});
|
|
1460
|
+
} catch (error) {
|
|
1461
|
+
if (error.code === 4001) {
|
|
1462
|
+
throw new ValidationError('User rejected revocation signature');
|
|
1463
|
+
}
|
|
1464
|
+
throw new ValidationError(`Failed to sign revocation: ${error.message}`);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const json = await this._makeRequest('POST', `/api/v1/proofs/revoke-self/${qHash}`, {
|
|
1468
|
+
walletAddress: address,
|
|
1469
|
+
signature,
|
|
1470
|
+
signedTimestamp,
|
|
1471
|
+
...(signerIsEvm ? {} : { chain, signatureMethod })
|
|
1472
|
+
});
|
|
1473
|
+
if (!json.success) {
|
|
1474
|
+
throw new ApiError(json.error?.message || 'Failed to revoke proof', json.error);
|
|
1475
|
+
}
|
|
1476
|
+
return true;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
_buildProofsByWalletQuery(options = {}) {
|
|
1480
|
+
const qs = [];
|
|
1481
|
+
if (options.limit) qs.push(`limit=${encodeURIComponent(String(options.limit))}`);
|
|
1482
|
+
const cursorRaw = options.cursor !== null && options.cursor !== undefined ? String(options.cursor).trim() : '';
|
|
1483
|
+
if (cursorRaw) qs.push(`cursor=${encodeURIComponent(cursorRaw)}`);
|
|
1484
|
+
else if (options.offset !== undefined && options.offset !== null) {
|
|
1485
|
+
qs.push(`offset=${encodeURIComponent(String(options.offset))}`);
|
|
1486
|
+
}
|
|
1487
|
+
if (options.q) qs.push(`q=${encodeURIComponent(String(options.q))}`);
|
|
1488
|
+
if (options.qHash) qs.push(`qHash=${encodeURIComponent(String(options.qHash).toLowerCase())}`);
|
|
1489
|
+
if (options.verifierId) qs.push(`verifierId=${encodeURIComponent(String(options.verifierId))}`);
|
|
1490
|
+
if (options.verifierIds) qs.push(`verifierIds=${encodeURIComponent(String(options.verifierIds))}`);
|
|
1491
|
+
if (options.tags) qs.push(`tags=${encodeURIComponent(String(options.tags))}`);
|
|
1492
|
+
if (options.tagPrefix) qs.push(`tagPrefix=${encodeURIComponent(String(options.tagPrefix))}`);
|
|
1493
|
+
if (options.tagContains) qs.push(`tagContains=${encodeURIComponent(String(options.tagContains))}`);
|
|
1494
|
+
if (options.tagPrefixesAll) qs.push(`tagPrefixesAll=${encodeURIComponent(String(options.tagPrefixesAll))}`);
|
|
1495
|
+
if (options.status) qs.push(`status=${encodeURIComponent(String(options.status))}`);
|
|
1496
|
+
if (options.appId) qs.push(`appId=${encodeURIComponent(String(options.appId))}`);
|
|
1497
|
+
if (options.chainCoverage) qs.push(`chainCoverage=${encodeURIComponent(String(options.chainCoverage))}`);
|
|
1498
|
+
if (options.privacyLevel) qs.push(`privacyLevel=${encodeURIComponent(String(options.privacyLevel))}`);
|
|
1499
|
+
if (options.includeHistory) qs.push('includeHistory=1');
|
|
1500
|
+
if (options.includeFacets) qs.push(`includeFacets=${encodeURIComponent(String(options.includeFacets))}`);
|
|
1501
|
+
if (options.visibility) qs.push(`visibility=${encodeURIComponent(String(options.visibility))}`);
|
|
1502
|
+
if (options.isPublicRead) qs.push('isPublicRead=1');
|
|
1503
|
+
return qs;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
async getProofsByWallet(walletAddress, options = {}) {
|
|
1507
|
+
if (!walletAddress || typeof walletAddress !== 'string') {
|
|
1508
|
+
throw new ValidationError('walletAddress is required');
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
const id = walletAddress.trim();
|
|
1512
|
+
const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
|
|
1513
|
+
|
|
1514
|
+
const qs = this._buildProofsByWalletQuery(options);
|
|
1515
|
+
|
|
1516
|
+
const query = qs.length ? `?${qs.join('&')}` : '';
|
|
1517
|
+
const response = await this._makeRequest(
|
|
1518
|
+
'GET',
|
|
1519
|
+
`/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`
|
|
1520
|
+
);
|
|
1521
|
+
|
|
1522
|
+
if (!response.success) {
|
|
1523
|
+
throw new ApiError(`Failed to get proofs: ${response.error?.message || 'Unknown error'}`, response.error);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const proofs = response.data?.proofs || response.data || response.proofs || [];
|
|
1527
|
+
return {
|
|
1528
|
+
success: true,
|
|
1529
|
+
proofs: Array.isArray(proofs) ? proofs : [],
|
|
1530
|
+
totalCount: typeof response.data?.totalCount === 'number' ? response.data.totalCount : null,
|
|
1531
|
+
hasMore: Boolean(response.data?.hasMore),
|
|
1532
|
+
nextOffset: response.data?.nextOffset ?? null,
|
|
1533
|
+
nextCursor:
|
|
1534
|
+
typeof response.data?.nextCursor === 'string' && response.data.nextCursor.trim()
|
|
1535
|
+
? response.data.nextCursor.trim()
|
|
1536
|
+
: null,
|
|
1537
|
+
facets: response.data?.facets || null,
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
async getPrivateProofsByWallet(walletAddress, options = {}, wallet = null) {
|
|
1542
|
+
if (!walletAddress || typeof walletAddress !== 'string') {
|
|
1543
|
+
throw new ValidationError('walletAddress is required');
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
const id = walletAddress.trim();
|
|
1547
|
+
const pathId = /^0x[a-fA-F0-9]{40}$/i.test(id) ? id.toLowerCase() : id;
|
|
1548
|
+
|
|
1549
|
+
const requestedIdentity = this._normalizeIdentity(id);
|
|
1550
|
+
|
|
1551
|
+
if (!wallet) {
|
|
1552
|
+
const defaultWallet = this._getDefaultBrowserWallet();
|
|
1553
|
+
if (!defaultWallet) {
|
|
1554
|
+
throw new ConfigurationError('No wallet provider available');
|
|
1555
|
+
}
|
|
1556
|
+
wallet = defaultWallet;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
const { signerWalletAddress, provider } = await this._resolveWalletSigner(wallet);
|
|
1560
|
+
|
|
1561
|
+
if (!signerWalletAddress || typeof signerWalletAddress !== 'string') {
|
|
1562
|
+
throw new ConfigurationError('No wallet accounts available');
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
const normalizedSigner = this._normalizeIdentity(signerWalletAddress);
|
|
1566
|
+
if (!normalizedSigner || normalizedSigner !== requestedIdentity) {
|
|
1567
|
+
throw new ValidationError('wallet must match walletAddress for private proof access');
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
const signerIsEvm = EVM_ADDRESS_RE.test(normalizedSigner);
|
|
1571
|
+
const chain = this._inferChainForAddress(normalizedSigner, options?.chain);
|
|
1572
|
+
const signatureMethod = (typeof options?.signatureMethod === 'string' && options.signatureMethod.trim())
|
|
1573
|
+
? options.signatureMethod.trim()
|
|
1574
|
+
: (signerIsEvm ? 'eip191' : 'ed25519');
|
|
1575
|
+
|
|
1576
|
+
const signedTimestamp = Date.now();
|
|
1577
|
+
const message = constructVerificationMessage({
|
|
1578
|
+
walletAddress: signerWalletAddress,
|
|
1579
|
+
signedTimestamp,
|
|
1580
|
+
data: { action: 'access_private_proofs_by_wallet', walletAddress: normalizedSigner },
|
|
1581
|
+
verifierIds: ['ownership-basic'],
|
|
1582
|
+
...(signerIsEvm ? { chainId: this._getHubChainId() } : { chain })
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
let signature;
|
|
1586
|
+
try {
|
|
1587
|
+
signature = await signMessage({
|
|
1588
|
+
provider,
|
|
1589
|
+
message,
|
|
1590
|
+
walletAddress: signerWalletAddress,
|
|
1591
|
+
...(signerIsEvm ? {} : { chain })
|
|
1592
|
+
});
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
if (error.code === 4001) {
|
|
1595
|
+
throw new ValidationError('User rejected signature request');
|
|
1596
|
+
}
|
|
1597
|
+
throw new ValidationError(`Failed to sign message: ${error.message}`);
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
const qs = this._buildProofsByWalletQuery(options);
|
|
1601
|
+
const query = qs.length ? `?${qs.join('&')}` : '';
|
|
1602
|
+
|
|
1603
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/by-wallet/${encodeURIComponent(pathId)}${query}`, null, {
|
|
1604
|
+
'x-wallet-address': signerWalletAddress,
|
|
1605
|
+
'x-signature': signature,
|
|
1606
|
+
'x-signed-timestamp': signedTimestamp.toString(),
|
|
1607
|
+
...(signerIsEvm ? {} : { 'x-chain': chain, 'x-signature-method': signatureMethod })
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
if (!response.success) {
|
|
1611
|
+
throw new ApiError(`Failed to get proofs: ${response.error?.message || 'Unauthorized'}`, response.error);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const proofs = response.data?.proofs || response.data || response.proofs || [];
|
|
1615
|
+
return {
|
|
1616
|
+
success: true,
|
|
1617
|
+
proofs: Array.isArray(proofs) ? proofs : [],
|
|
1618
|
+
totalCount: typeof response.data?.totalCount === 'number' ? response.data.totalCount : null,
|
|
1619
|
+
hasMore: Boolean(response.data?.hasMore),
|
|
1620
|
+
nextOffset: response.data?.nextOffset ?? null,
|
|
1621
|
+
nextCursor:
|
|
1622
|
+
typeof response.data?.nextCursor === 'string' && response.data.nextCursor.trim()
|
|
1623
|
+
? response.data.nextCursor.trim()
|
|
1624
|
+
: null
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
async gateCheck(params = {}) {
|
|
1629
|
+
const address = (params.address || '').toString();
|
|
1630
|
+
if (!validateUniversalAddress(address, params.chain)) {
|
|
1631
|
+
throw new ValidationError('Valid address is required');
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
const gateIdParam = typeof params.gateId === 'string' ? params.gateId.trim() : '';
|
|
1635
|
+
const verifierIds = gateIdParam ? undefined : params.verifierIds;
|
|
1636
|
+
|
|
1637
|
+
const qs = new URLSearchParams();
|
|
1638
|
+
qs.set('address', address);
|
|
1639
|
+
|
|
1640
|
+
const setIfPresent = (key, value) => {
|
|
1641
|
+
if (value === undefined || value === null) return;
|
|
1642
|
+
const str = typeof value === 'string' ? value : String(value);
|
|
1643
|
+
if (str.length === 0) return;
|
|
1644
|
+
qs.set(key, str);
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
const setBoolIfPresent = (key, value) => {
|
|
1648
|
+
if (value === undefined || value === null) return;
|
|
1649
|
+
qs.set(key, value ? 'true' : 'false');
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
const setCsvIfPresent = (key, value) => {
|
|
1653
|
+
if (value === undefined || value === null) return;
|
|
1654
|
+
if (Array.isArray(value)) {
|
|
1655
|
+
const items = value.map(v => String(v).trim()).filter(Boolean);
|
|
1656
|
+
if (items.length) qs.set(key, items.join(','));
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
setIfPresent(key, value);
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
setIfPresent('gateId', gateIdParam);
|
|
1663
|
+
setCsvIfPresent('verifierIds', verifierIds);
|
|
1664
|
+
setBoolIfPresent('requireAll', params.requireAll);
|
|
1665
|
+
setIfPresent('minCount', params.minCount);
|
|
1666
|
+
setIfPresent('sinceDays', params.sinceDays);
|
|
1667
|
+
setIfPresent('since', params.since);
|
|
1668
|
+
setIfPresent('limit', params.limit);
|
|
1669
|
+
setBoolIfPresent('includePrivate', params.includePrivate);
|
|
1670
|
+
setBoolIfPresent('includeQHashes', params.includeQHashes);
|
|
1671
|
+
|
|
1672
|
+
setIfPresent('referenceType', params.referenceType);
|
|
1673
|
+
setIfPresent('referenceId', params.referenceId);
|
|
1674
|
+
setIfPresent('tag', params.tag);
|
|
1675
|
+
setCsvIfPresent('tags', params.tags);
|
|
1676
|
+
setIfPresent('contentType', params.contentType);
|
|
1677
|
+
setIfPresent('content', params.content);
|
|
1678
|
+
setIfPresent('contentHash', params.contentHash);
|
|
1679
|
+
|
|
1680
|
+
setIfPresent('contractAddress', params.contractAddress);
|
|
1681
|
+
setIfPresent('tokenId', params.tokenId);
|
|
1682
|
+
setIfPresent('chainId', params.chainId);
|
|
1683
|
+
setIfPresent('domain', params.domain);
|
|
1684
|
+
setIfPresent('minBalance', params.minBalance);
|
|
1685
|
+
|
|
1686
|
+
setIfPresent('provider', params.provider);
|
|
1687
|
+
setIfPresent('handle', params.handle);
|
|
1688
|
+
setIfPresent('namespace', params.namespace);
|
|
1689
|
+
setIfPresent('ownerAddress', params.ownerAddress);
|
|
1690
|
+
setIfPresent('riskLevel', params.riskLevel);
|
|
1691
|
+
setBoolIfPresent('sanctioned', params.sanctioned);
|
|
1692
|
+
setBoolIfPresent('poisoned', params.poisoned);
|
|
1693
|
+
setIfPresent('primaryWalletAddress', params.primaryWalletAddress);
|
|
1694
|
+
setIfPresent('secondaryWalletAddress', params.secondaryWalletAddress);
|
|
1695
|
+
setIfPresent('verificationMethod', params.verificationMethod);
|
|
1696
|
+
setIfPresent('neusPersonhoodId', params.neusPersonhoodId);
|
|
1697
|
+
|
|
1698
|
+
let headersOverride = null;
|
|
1699
|
+
if (params.includePrivate === true) {
|
|
1700
|
+
const provided = params.privateAuth && typeof params.privateAuth === 'object' ? params.privateAuth : null;
|
|
1701
|
+
let auth = provided;
|
|
1702
|
+
if (!auth) {
|
|
1703
|
+
const walletCandidate = params.wallet || this._getDefaultBrowserWallet();
|
|
1704
|
+
if (walletCandidate) {
|
|
1705
|
+
auth = await this._buildPrivateGateAuth({
|
|
1706
|
+
address,
|
|
1707
|
+
wallet: walletCandidate,
|
|
1708
|
+
chain: params.chain,
|
|
1709
|
+
signatureMethod: params.signatureMethod
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
if (auth) {
|
|
1714
|
+
const normalizedAuthWallet = this._normalizeIdentity(auth.walletAddress);
|
|
1715
|
+
const normalizedAddress = this._normalizeIdentity(address);
|
|
1716
|
+
if (!normalizedAuthWallet || normalizedAuthWallet !== normalizedAddress) {
|
|
1717
|
+
throw new ValidationError('privateAuth.walletAddress must match address when includePrivate=true');
|
|
1718
|
+
}
|
|
1719
|
+
const authChain = (typeof auth.chain === 'string' && auth.chain.includes(':')) ? auth.chain.trim() : null;
|
|
1720
|
+
const authSignatureMethod = (typeof auth.signatureMethod === 'string' && auth.signatureMethod.trim())
|
|
1721
|
+
? auth.signatureMethod.trim()
|
|
1722
|
+
: null;
|
|
1723
|
+
headersOverride = {
|
|
1724
|
+
'x-wallet-address': String(auth.walletAddress),
|
|
1725
|
+
'x-signature': String(auth.signature),
|
|
1726
|
+
'x-signed-timestamp': String(auth.signedTimestamp),
|
|
1727
|
+
...(authChain ? { 'x-chain': authChain } : {}),
|
|
1728
|
+
...(authSignatureMethod ? { 'x-signature-method': authSignatureMethod } : {})
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
let mergedHeaders = headersOverride;
|
|
1734
|
+
if (!mergedHeaders && !gateIdParam) {
|
|
1735
|
+
try {
|
|
1736
|
+
const sponsorHeaders = await this._resolveSponsorGrantHeaders(
|
|
1737
|
+
Array.isArray(verifierIds)
|
|
1738
|
+
? verifierIds
|
|
1739
|
+
: (verifierIds ? [verifierIds] : [])
|
|
1740
|
+
);
|
|
1741
|
+
if (sponsorHeaders && Object.keys(sponsorHeaders).length > 0) {
|
|
1742
|
+
mergedHeaders = sponsorHeaders;
|
|
1743
|
+
}
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
this._log('Sponsor grant unavailable for gateCheck (continuing without)', error?.message || String(error));
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
const response = await this._makeRequest('GET', `/api/v1/proofs/check?${qs.toString()}`, null, mergedHeaders);
|
|
1750
|
+
if (!response.success) {
|
|
1751
|
+
throw new ApiError(`Gate check failed: ${response.error?.message || 'Unknown error'}`, response.error);
|
|
1752
|
+
}
|
|
1753
|
+
return response;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
/**
|
|
1757
|
+
* Get the public snapshot of a published gate: requirements, charge, schedule,
|
|
1758
|
+
* checkout plan, and reward presence. Never returns the secret reward value —
|
|
1759
|
+
* that is delivered post-verify via fulfillGate().
|
|
1760
|
+
*
|
|
1761
|
+
* @param {string} gateId Published gate handle
|
|
1762
|
+
* @returns {Promise<object>} Public gate snapshot
|
|
1763
|
+
*/
|
|
1764
|
+
async getGate(gateId) {
|
|
1765
|
+
const id = String(gateId || '').trim();
|
|
1766
|
+
if (!id || id.length > 80 || !/^[a-zA-Z0-9:_-]+$/.test(id)) {
|
|
1767
|
+
throw new ValidationError('Valid gateId is required');
|
|
1768
|
+
}
|
|
1769
|
+
const response = await this._makeRequest('GET', `/api/v1/profile/gates/${encodeURIComponent(id)}`);
|
|
1770
|
+
if (!response.success || !response.data?.gate) {
|
|
1771
|
+
throw new ApiError(`Gate lookup failed: ${response.error?.message || 'Gate not found'}`, response.error);
|
|
1772
|
+
}
|
|
1773
|
+
return response.data.gate;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/**
|
|
1777
|
+
* Post-verify reward delivery for hosted gate checkout. Requires a verified
|
|
1778
|
+
* proof (qHash) for the gate; paid gates also require payment evidence
|
|
1779
|
+
* (paymentCheckoutSessionId for card, or paymentTxHash for USDC).
|
|
1780
|
+
*
|
|
1781
|
+
* @param {object} params
|
|
1782
|
+
* @param {string} params.gateId Published gate handle
|
|
1783
|
+
* @param {string} params.qHash Verified proof receipt id
|
|
1784
|
+
* @param {string} [params.walletAddress] Wallet bound to the proof (required without a session cookie)
|
|
1785
|
+
* @param {string} [params.paymentCheckoutSessionId] Stripe checkout session id (card rail)
|
|
1786
|
+
* @param {string} [params.paymentTxHash] USDC payment transaction hash (wallet rail)
|
|
1787
|
+
* @returns {Promise<object>} `{ success, data: { gateId, qHash, fulfillment, successReturnUrl? } }`
|
|
1788
|
+
*/
|
|
1789
|
+
async fulfillGate(params = {}) {
|
|
1790
|
+
const gateId = String(params.gateId || '').trim();
|
|
1791
|
+
if (!gateId || gateId.length > 80 || !/^[a-zA-Z0-9:_-]+$/.test(gateId)) {
|
|
1792
|
+
throw new ValidationError('Valid gateId is required');
|
|
1793
|
+
}
|
|
1794
|
+
const qHash = String(params.qHash || '').trim();
|
|
1795
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(qHash)) {
|
|
1796
|
+
throw new ValidationError('Valid qHash is required');
|
|
1797
|
+
}
|
|
1798
|
+
const body = { qHash };
|
|
1799
|
+
const walletAddress = String(params.walletAddress || '').trim();
|
|
1800
|
+
if (walletAddress) body.walletAddress = walletAddress;
|
|
1801
|
+
const paymentCheckoutSessionId = String(params.paymentCheckoutSessionId || '').trim();
|
|
1802
|
+
if (paymentCheckoutSessionId) body.paymentCheckoutSessionId = paymentCheckoutSessionId;
|
|
1803
|
+
const paymentTxHash = String(params.paymentTxHash || '').trim();
|
|
1804
|
+
if (paymentTxHash) body.paymentTxHash = paymentTxHash;
|
|
1805
|
+
|
|
1806
|
+
const response = await this._makeRequest(
|
|
1807
|
+
'POST',
|
|
1808
|
+
`/api/v1/profile/gates/${encodeURIComponent(gateId)}/fulfill`,
|
|
1809
|
+
body
|
|
1810
|
+
);
|
|
1811
|
+
if (!response.success) {
|
|
1812
|
+
throw new ApiError(`Gate fulfillment failed: ${response.error?.message || 'Unknown error'}`, response.error);
|
|
1813
|
+
}
|
|
1814
|
+
return response;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
async checkGate(params) {
|
|
1818
|
+
const { walletAddress, requirements, proofs: preloadedProofs } = params;
|
|
1819
|
+
|
|
1820
|
+
if (!validateUniversalAddress(walletAddress)) {
|
|
1821
|
+
throw new ValidationError('Valid walletAddress is required');
|
|
1822
|
+
}
|
|
1823
|
+
if (!Array.isArray(requirements) || requirements.length === 0) {
|
|
1824
|
+
throw new ValidationError('requirements array is required and must not be empty');
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
let proofs = preloadedProofs;
|
|
1828
|
+
if (!proofs) {
|
|
1829
|
+
const result = await this.getProofsByWallet(walletAddress, { limit: 100 });
|
|
1830
|
+
proofs = result.proofs;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
const now = Date.now();
|
|
1834
|
+
const existing = {};
|
|
1835
|
+
const missing = [];
|
|
1836
|
+
|
|
1837
|
+
for (const req of requirements) {
|
|
1838
|
+
const { verifierId, maxAgeMs, optional = false, minCount = 1, match } = req;
|
|
1839
|
+
|
|
1840
|
+
const matchingProofs = (proofs || []).filter(proof => {
|
|
1841
|
+
const verifiedVerifiers = proof.verifiedVerifiers || [];
|
|
1842
|
+
const verifier = verifiedVerifiers.find(
|
|
1843
|
+
v => v.verifierId === verifierId && v.verified === true
|
|
1844
|
+
);
|
|
1845
|
+
if (!verifier) return false;
|
|
1846
|
+
|
|
1847
|
+
if (proof.revokedAt) return false;
|
|
1848
|
+
|
|
1849
|
+
if (maxAgeMs) {
|
|
1850
|
+
const proofTimestamp = proof.createdAt || proof.signedTimestamp || 0;
|
|
1851
|
+
const proofAge = now - proofTimestamp;
|
|
1852
|
+
if (proofAge > maxAgeMs) return false;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
if (match && typeof match === 'object') {
|
|
1856
|
+
const data = verifier.data || {};
|
|
1857
|
+
const input = data.input || {};
|
|
1858
|
+
const matchObj = Array.isArray(match)
|
|
1859
|
+
? Object.fromEntries(
|
|
1860
|
+
match
|
|
1861
|
+
.filter((m) => m && m.path && String(m.value ?? '').trim() !== '')
|
|
1862
|
+
.map((m) => [String(m.path).trim(), m.value])
|
|
1863
|
+
)
|
|
1864
|
+
: match;
|
|
1865
|
+
|
|
1866
|
+
for (const [key, expected] of Object.entries(matchObj)) {
|
|
1867
|
+
let actualValue = null;
|
|
1868
|
+
|
|
1869
|
+
if (key.includes('.')) {
|
|
1870
|
+
const parts = key.split('.');
|
|
1871
|
+
let current = data;
|
|
1872
|
+
|
|
1873
|
+
if (parts[0] === 'input' && input) {
|
|
1874
|
+
current = input;
|
|
1875
|
+
parts.shift();
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
for (const part of parts) {
|
|
1879
|
+
if (current && typeof current === 'object' && part in current) {
|
|
1880
|
+
current = current[part];
|
|
1881
|
+
} else {
|
|
1882
|
+
current = undefined;
|
|
1883
|
+
break;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
actualValue = current;
|
|
1887
|
+
} else {
|
|
1888
|
+
actualValue = input[key] || data[key];
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
if (key === 'content' && actualValue === undefined) {
|
|
1892
|
+
actualValue = data.reference?.id || data.content;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
if (actualValue === undefined) {
|
|
1896
|
+
const claims = data.claims || {};
|
|
1897
|
+
if (key === 'contractAddress') {
|
|
1898
|
+
actualValue = input.contractAddress || data.contractAddress;
|
|
1899
|
+
} else if (key === 'tokenId') {
|
|
1900
|
+
actualValue = input.tokenId || data.tokenId;
|
|
1901
|
+
} else if (key === 'chainId') {
|
|
1902
|
+
actualValue = input.chainId || data.chainId;
|
|
1903
|
+
} else if (key === 'ownerAddress') {
|
|
1904
|
+
actualValue = input.ownerAddress || data.owner || data.walletAddress;
|
|
1905
|
+
} else if (key === 'minBalance') {
|
|
1906
|
+
actualValue = input.minBalance || data.onChainData?.requiredMinBalance || data.minBalance;
|
|
1907
|
+
} else if (key === 'primaryWalletAddress') {
|
|
1908
|
+
actualValue = data.primaryWalletAddress;
|
|
1909
|
+
} else if (key === 'secondaryWalletAddress') {
|
|
1910
|
+
actualValue = data.secondaryWalletAddress;
|
|
1911
|
+
} else if (key === 'verificationMethod') {
|
|
1912
|
+
actualValue = data.verificationMethod;
|
|
1913
|
+
} else if (key === 'domain') {
|
|
1914
|
+
actualValue = data.domain;
|
|
1915
|
+
} else if (key === 'handle') {
|
|
1916
|
+
actualValue = data.handle || data.pseudonymId;
|
|
1917
|
+
} else if (key === 'namespace') {
|
|
1918
|
+
actualValue =
|
|
1919
|
+
data.namespace !== undefined && data.namespace !== null && data.namespace !== ''
|
|
1920
|
+
? data.namespace
|
|
1921
|
+
: 'neus';
|
|
1922
|
+
} else if (key === 'claims.sanctions_passed') {
|
|
1923
|
+
actualValue = claims.sanctions_passed ?? claims.sanctionsPassed;
|
|
1924
|
+
} else if (key === 'claims.age_min') {
|
|
1925
|
+
actualValue = claims.age_min ?? claims.ageMin;
|
|
1926
|
+
} else if (key === 'neusPersonhoodId') {
|
|
1927
|
+
actualValue = data.neusPersonhoodId;
|
|
1928
|
+
} else if (key === 'riskLevel') {
|
|
1929
|
+
actualValue = data.riskLevel;
|
|
1930
|
+
} else if (key === 'sanctioned') {
|
|
1931
|
+
actualValue = data.sanctioned;
|
|
1932
|
+
} else if (key === 'poisoned') {
|
|
1933
|
+
actualValue = data.poisoned;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
if (key === 'contentHash' && actualValue === undefined && data.content) {
|
|
1938
|
+
try {
|
|
1939
|
+
let hash = 0;
|
|
1940
|
+
const str = String(data.content);
|
|
1941
|
+
for (let i = 0; i < str.length; i++) {
|
|
1942
|
+
const char = str.charCodeAt(i);
|
|
1943
|
+
hash = ((hash << 5) - hash) + char;
|
|
1944
|
+
hash = hash & hash;
|
|
1945
|
+
}
|
|
1946
|
+
actualValue = `0x${ Math.abs(hash).toString(16).padStart(64, '0').substring(0, 66)}`;
|
|
1947
|
+
} catch {
|
|
1948
|
+
actualValue = String(data.content);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
let normalizedActual = actualValue;
|
|
1953
|
+
let normalizedExpected = expected;
|
|
1954
|
+
|
|
1955
|
+
if (actualValue === undefined || actualValue === null) {
|
|
1956
|
+
return false;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
if (typeof actualValue === 'boolean' || (key && (key.includes('sanctions') || key.includes('sanctioned') || key.includes('poisoned')))) {
|
|
1960
|
+
const bActual = Boolean(actualValue);
|
|
1961
|
+
const bExpected = expected === true || expected === 'true' || String(expected).toLowerCase() === 'true';
|
|
1962
|
+
if (bActual !== bExpected) return false;
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
if (key === 'chainId' || (key === 'tokenId' && (typeof actualValue === 'number' || !isNaN(Number(actualValue))))) {
|
|
1967
|
+
normalizedActual = Number(actualValue);
|
|
1968
|
+
normalizedExpected = Number(expected);
|
|
1969
|
+
if (isNaN(normalizedActual) || isNaN(normalizedExpected)) return false;
|
|
1970
|
+
}
|
|
1971
|
+
else if (typeof actualValue === 'string' && (actualValue.startsWith('0x') || actualValue.length > 20)) {
|
|
1972
|
+
normalizedActual = actualValue.toLowerCase();
|
|
1973
|
+
normalizedExpected = typeof expected === 'string' ? String(expected).toLowerCase() : expected;
|
|
1974
|
+
}
|
|
1975
|
+
else {
|
|
1976
|
+
normalizedActual = actualValue;
|
|
1977
|
+
normalizedExpected = expected;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
if (normalizedActual !== normalizedExpected) {
|
|
1981
|
+
return false;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
return true;
|
|
1987
|
+
});
|
|
1988
|
+
|
|
1989
|
+
if (matchingProofs.length >= minCount) {
|
|
1990
|
+
const sorted = matchingProofs.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
1991
|
+
existing[verifierId] = sorted[0];
|
|
1992
|
+
} else if (!optional) {
|
|
1993
|
+
missing.push(req);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
return {
|
|
1998
|
+
satisfied: missing.length === 0,
|
|
1999
|
+
missing,
|
|
2000
|
+
existing,
|
|
2001
|
+
allProofs: proofs
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
async _getWalletAddress() {
|
|
2006
|
+
if (typeof window === 'undefined' || !window.ethereum) {
|
|
2007
|
+
throw new ConfigurationError('No Web3 wallet detected');
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
const accounts = await window.ethereum.request({ method: 'eth_accounts' });
|
|
2011
|
+
if (!accounts || accounts.length === 0) {
|
|
2012
|
+
throw new ConfigurationError('No wallet accounts available');
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
return accounts[0];
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
async _makeRequest(method, endpoint, data = null, headersOverride = null) {
|
|
2019
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
2020
|
+
|
|
2021
|
+
const controller = new AbortController();
|
|
2022
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
2023
|
+
|
|
2024
|
+
const options = {
|
|
2025
|
+
method,
|
|
2026
|
+
headers: { ...this.defaultHeaders, ...(headersOverride || {}) },
|
|
2027
|
+
signal: controller.signal
|
|
2028
|
+
};
|
|
2029
|
+
|
|
2030
|
+
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
2031
|
+
options.body = JSON.stringify(data);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
this._log(`${method} ${endpoint}`, data ? { requestBodyKeys: Object.keys(data) } : {});
|
|
2035
|
+
|
|
2036
|
+
try {
|
|
2037
|
+
const response = await fetch(url, options);
|
|
2038
|
+
clearTimeout(timeoutId);
|
|
2039
|
+
|
|
2040
|
+
let responseData;
|
|
2041
|
+
try {
|
|
2042
|
+
responseData = await response.json();
|
|
2043
|
+
} catch {
|
|
2044
|
+
responseData = { error: { message: 'Invalid JSON response' } };
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
if (!response.ok) {
|
|
2048
|
+
throw ApiError.fromResponse(response, responseData);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
return responseData;
|
|
2052
|
+
|
|
2053
|
+
} catch (error) {
|
|
2054
|
+
clearTimeout(timeoutId);
|
|
2055
|
+
|
|
2056
|
+
if (error.name === 'AbortError') {
|
|
2057
|
+
throw new NetworkError(`Request timeout after ${this.config.timeout}ms`);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
if (error instanceof ApiError) {
|
|
2061
|
+
throw error;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
throw new NetworkError(`Network error: ${error.message}`);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
_formatResponse(response) {
|
|
2069
|
+
const qHash = response?.data?.qHash ||
|
|
2070
|
+
response?.qHash ||
|
|
2071
|
+
response?.data?.resource?.qHash ||
|
|
2072
|
+
response?.data?.id;
|
|
2073
|
+
|
|
2074
|
+
const status = response?.data?.status ||
|
|
2075
|
+
response?.status ||
|
|
2076
|
+
response?.data?.resource?.status ||
|
|
2077
|
+
(response?.success ? 'completed' : 'unknown');
|
|
2078
|
+
|
|
2079
|
+
return {
|
|
2080
|
+
success: response.success,
|
|
2081
|
+
qHash,
|
|
2082
|
+
status,
|
|
2083
|
+
data: response.data,
|
|
2084
|
+
message: response.message,
|
|
2085
|
+
timestamp: Date.now(),
|
|
2086
|
+
proofUrl: qHash ? `${this.baseUrl}/api/v1/proofs/${qHash}` : null
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
_isTerminalStatus(status) {
|
|
2091
|
+
const terminalStates = [
|
|
2092
|
+
'verified',
|
|
2093
|
+
'verified_crosschain_propagated',
|
|
2094
|
+
'completed_all_successful',
|
|
2095
|
+
'failed',
|
|
2096
|
+
'error',
|
|
2097
|
+
'rejected',
|
|
2098
|
+
'cancelled'
|
|
2099
|
+
];
|
|
2100
|
+
return typeof status === 'string' && terminalStates.some(state => status.includes(state));
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
_log(message, data = {}) {
|
|
2104
|
+
if (this.config.enableLogging) {
|
|
2105
|
+
try {
|
|
2106
|
+
const prefix = '[neus/sdk]';
|
|
2107
|
+
if (data && Object.keys(data).length > 0) {
|
|
2108
|
+
// eslint-disable-next-line no-console -- gated debug logging
|
|
2109
|
+
console.log(prefix, message, data);
|
|
2110
|
+
} else {
|
|
2111
|
+
// eslint-disable-next-line no-console -- gated debug logging
|
|
2112
|
+
console.log(prefix, message);
|
|
2113
|
+
}
|
|
2114
|
+
} catch {
|
|
2115
|
+
void 0;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
export { PORTABLE_PROOF_SIGNER_HEADER, constructVerificationMessage };
|