@moltlaunch/sdk 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +184 -275
- package/dist/client.d.ts +69 -0
- package/dist/client.js +325 -0
- package/dist/idl/moltlaunch.json +1549 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +63 -0
- package/dist/pda.d.ts +26 -0
- package/dist/pda.js +45 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.js +54 -0
- package/package.json +24 -9
- package/src/idl/moltlaunch.json +1549 -0
- package/examples/agent-config.json +0 -12
- package/src/cli.ts +0 -158
- package/src/index.d.ts +0 -258
- package/src/index.js +0 -1117
- package/src/index.ts +0 -68
- package/src/launcher.ts +0 -263
- package/src/types.ts +0 -122
- package/src/verification.ts +0 -228
- package/test/basic.js +0 -71
- package/tsconfig.json +0 -17
package/src/index.js
DELETED
|
@@ -1,1117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MoltLaunch SDK
|
|
3
|
-
* On-chain AI verification for AI agents
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* const { MoltLaunch } = require('@moltlaunch/sdk');
|
|
7
|
-
* const ml = new MoltLaunch();
|
|
8
|
-
* const result = await ml.verify({ agentId: 'my-agent', capabilities: ['trading'] });
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const DEFAULT_BASE_URL = 'https://web-production-419d9.up.railway.app';
|
|
12
|
-
|
|
13
|
-
class MoltLaunch {
|
|
14
|
-
/**
|
|
15
|
-
* Create a MoltLaunch client
|
|
16
|
-
* @param {Object} options - Configuration options
|
|
17
|
-
* @param {string} [options.baseUrl] - API base URL (default: production)
|
|
18
|
-
* @param {string} [options.apiKey] - Optional API key for premium features
|
|
19
|
-
*/
|
|
20
|
-
constructor(options = {}) {
|
|
21
|
-
this.baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
22
|
-
this.apiKey = options.apiKey || null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Get on-chain AI deployment info
|
|
27
|
-
* @returns {Promise<OnChainInfo>}
|
|
28
|
-
*/
|
|
29
|
-
async getOnChainInfo() {
|
|
30
|
-
const res = await fetch(`${this.baseUrl}/api/onchain-ai`);
|
|
31
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
32
|
-
return res.json();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Verify an agent using on-chain AI
|
|
37
|
-
* @param {VerifyOptions} options - Verification options
|
|
38
|
-
* @returns {Promise<VerificationResult>}
|
|
39
|
-
*/
|
|
40
|
-
/**
|
|
41
|
-
* Generate a random nonce for replay protection
|
|
42
|
-
* @returns {string}
|
|
43
|
-
*/
|
|
44
|
-
generateNonce() {
|
|
45
|
-
const bytes = new Uint8Array(16);
|
|
46
|
-
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
47
|
-
crypto.getRandomValues(bytes);
|
|
48
|
-
} else {
|
|
49
|
-
// Node.js fallback
|
|
50
|
-
const nodeCrypto = require('crypto');
|
|
51
|
-
const buf = nodeCrypto.randomBytes(16);
|
|
52
|
-
bytes.set(buf);
|
|
53
|
-
}
|
|
54
|
-
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Verify an agent using on-chain AI (v3.0 with security features)
|
|
59
|
-
* @param {VerifyOptions} options - Verification options
|
|
60
|
-
* @returns {Promise<VerificationResult>}
|
|
61
|
-
*/
|
|
62
|
-
async verify(options) {
|
|
63
|
-
const {
|
|
64
|
-
agentId,
|
|
65
|
-
wallet,
|
|
66
|
-
capabilities = [],
|
|
67
|
-
codeUrl,
|
|
68
|
-
documentation = false,
|
|
69
|
-
testCoverage = 0,
|
|
70
|
-
codeLines = 0,
|
|
71
|
-
apiEndpoint,
|
|
72
|
-
// v3.0 security options
|
|
73
|
-
secureMode = false,
|
|
74
|
-
nonce,
|
|
75
|
-
timestamp,
|
|
76
|
-
signature,
|
|
77
|
-
validityDays = 30
|
|
78
|
-
} = options;
|
|
79
|
-
|
|
80
|
-
if (!agentId) throw new Error('agentId is required');
|
|
81
|
-
|
|
82
|
-
// Build request body
|
|
83
|
-
const body = {
|
|
84
|
-
agentId,
|
|
85
|
-
wallet,
|
|
86
|
-
capabilities,
|
|
87
|
-
codeUrl,
|
|
88
|
-
documentation,
|
|
89
|
-
testCoverage,
|
|
90
|
-
codeLines,
|
|
91
|
-
apiEndpoint
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Add v3.0 security fields if secure mode
|
|
95
|
-
if (secureMode) {
|
|
96
|
-
body.nonce = nonce || this.generateNonce();
|
|
97
|
-
body.timestamp = timestamp || Math.floor(Date.now() / 1000);
|
|
98
|
-
body.validityDays = validityDays;
|
|
99
|
-
if (signature) body.signature = signature;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const res = await fetch(`${this.baseUrl}/api/verify/deep`, {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
headers: {
|
|
105
|
-
'Content-Type': 'application/json',
|
|
106
|
-
...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` })
|
|
107
|
-
},
|
|
108
|
-
body: JSON.stringify(body)
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (!res.ok) {
|
|
112
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
113
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const data = await res.json();
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
agentId: data.agentId,
|
|
120
|
-
verified: data.score >= 60,
|
|
121
|
-
passed: data.passed,
|
|
122
|
-
score: data.score,
|
|
123
|
-
tier: data.scoreTier,
|
|
124
|
-
features: data.features,
|
|
125
|
-
onChainAI: data.onChainAI,
|
|
126
|
-
attestation: data.attestation,
|
|
127
|
-
security: data.security,
|
|
128
|
-
raw: data
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Verify with secure mode enabled (replay-protected)
|
|
134
|
-
* @param {VerifyOptions} options - Verification options
|
|
135
|
-
* @returns {Promise<VerificationResult>}
|
|
136
|
-
*/
|
|
137
|
-
async verifySecure(options) {
|
|
138
|
-
return this.verify({ ...options, secureMode: true });
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Check if an attestation is revoked
|
|
143
|
-
* @param {string} attestationHash - Attestation hash
|
|
144
|
-
* @returns {Promise<{revoked: boolean, checkedAt: string}>}
|
|
145
|
-
*/
|
|
146
|
-
async checkRevocation(attestationHash) {
|
|
147
|
-
const res = await fetch(`${this.baseUrl}/api/verify/revoked/${attestationHash}`);
|
|
148
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
149
|
-
return res.json();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Renew verification before expiry
|
|
154
|
-
* @param {string} agentId - Agent ID
|
|
155
|
-
* @param {object} options - Additional options
|
|
156
|
-
* @returns {Promise<VerificationResult>}
|
|
157
|
-
*/
|
|
158
|
-
async renew(agentId, options = {}) {
|
|
159
|
-
const res = await fetch(`${this.baseUrl}/api/verify/renew/${agentId}`, {
|
|
160
|
-
method: 'POST',
|
|
161
|
-
headers: { 'Content-Type': 'application/json' },
|
|
162
|
-
body: JSON.stringify(options)
|
|
163
|
-
});
|
|
164
|
-
if (!res.ok) {
|
|
165
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
166
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
167
|
-
}
|
|
168
|
-
return res.json();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Get verification status for an agent
|
|
173
|
-
* @param {string} agentId - Agent ID
|
|
174
|
-
* @returns {Promise<StatusResult>}
|
|
175
|
-
*/
|
|
176
|
-
async getStatus(agentId) {
|
|
177
|
-
const res = await fetch(`${this.baseUrl}/api/verify/status/${encodeURIComponent(agentId)}`);
|
|
178
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
179
|
-
return res.json();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get verification status for multiple agents
|
|
184
|
-
* @param {string[]} agentIds - Array of agent IDs
|
|
185
|
-
* @returns {Promise<BatchStatusResult>}
|
|
186
|
-
*/
|
|
187
|
-
async getStatusBatch(agentIds) {
|
|
188
|
-
const res = await fetch(`${this.baseUrl}/api/verify/status/batch`, {
|
|
189
|
-
method: 'POST',
|
|
190
|
-
headers: { 'Content-Type': 'application/json' },
|
|
191
|
-
body: JSON.stringify({ agentIds })
|
|
192
|
-
});
|
|
193
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
194
|
-
return res.json();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Apply agent to a staking pool
|
|
199
|
-
* @param {PoolApplyOptions} options - Pool application options
|
|
200
|
-
* @returns {Promise<PoolApplyResult>}
|
|
201
|
-
*/
|
|
202
|
-
async applyToPool(options) {
|
|
203
|
-
const { agentId, wallet, topic, strategy } = options;
|
|
204
|
-
|
|
205
|
-
const res = await fetch(`${this.baseUrl}/api/pool/apply`, {
|
|
206
|
-
method: 'POST',
|
|
207
|
-
headers: { 'Content-Type': 'application/json' },
|
|
208
|
-
body: JSON.stringify({ agentId, wallet, topic, strategy })
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
if (!res.ok) {
|
|
212
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
213
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return res.json();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Get pool information
|
|
221
|
-
* @param {string} [topic] - Optional topic filter
|
|
222
|
-
* @returns {Promise<PoolInfo>}
|
|
223
|
-
*/
|
|
224
|
-
async getPools(topic) {
|
|
225
|
-
const url = topic
|
|
226
|
-
? `${this.baseUrl}/api/pools/${encodeURIComponent(topic)}`
|
|
227
|
-
: `${this.baseUrl}/api/pools`;
|
|
228
|
-
const res = await fetch(url);
|
|
229
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
230
|
-
return res.json();
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get leaderboard
|
|
235
|
-
* @returns {Promise<LeaderboardResult>}
|
|
236
|
-
*/
|
|
237
|
-
async getLeaderboard() {
|
|
238
|
-
const res = await fetch(`${this.baseUrl}/api/pools/leaderboard`);
|
|
239
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
240
|
-
return res.json();
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Check API health
|
|
245
|
-
* @returns {Promise<boolean>}
|
|
246
|
-
*/
|
|
247
|
-
async isHealthy() {
|
|
248
|
-
try {
|
|
249
|
-
const res = await fetch(`${this.baseUrl}/api/health`);
|
|
250
|
-
return res.ok;
|
|
251
|
-
} catch {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// ==========================================
|
|
257
|
-
// STARK PROOFS (v3.3 - Privacy-Preserving)
|
|
258
|
-
// ==========================================
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Generate a STARK threshold proof
|
|
262
|
-
* Proves "score >= threshold" without revealing exact score
|
|
263
|
-
* @param {string} agentId - Agent ID
|
|
264
|
-
* @param {object} options - Proof options
|
|
265
|
-
* @param {number} [options.threshold=60] - Minimum score to prove
|
|
266
|
-
* @returns {Promise<STARKProof>}
|
|
267
|
-
*/
|
|
268
|
-
async generateProof(agentId, options = {}) {
|
|
269
|
-
const { threshold = 60 } = options;
|
|
270
|
-
|
|
271
|
-
const res = await fetch(`${this.baseUrl}/api/stark/generate/${encodeURIComponent(agentId)}`, {
|
|
272
|
-
method: 'POST',
|
|
273
|
-
headers: { 'Content-Type': 'application/json' },
|
|
274
|
-
body: JSON.stringify({ threshold })
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
if (!res.ok) {
|
|
278
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
279
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return res.json();
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Generate a consistency proof
|
|
287
|
-
* Proves "maintained >= threshold for N periods" without revealing individual scores
|
|
288
|
-
* @param {string} agentId - Agent ID
|
|
289
|
-
* @param {object} options - Proof options
|
|
290
|
-
* @param {number} [options.threshold=60] - Minimum score threshold
|
|
291
|
-
* @param {number} [options.days=30] - Number of days to prove
|
|
292
|
-
* @returns {Promise<ConsistencyProof>}
|
|
293
|
-
*/
|
|
294
|
-
async generateConsistencyProof(agentId, options = {}) {
|
|
295
|
-
const { threshold = 60, days = 30 } = options;
|
|
296
|
-
|
|
297
|
-
const res = await fetch(`${this.baseUrl}/api/stark/consistency/${encodeURIComponent(agentId)}`, {
|
|
298
|
-
method: 'POST',
|
|
299
|
-
headers: { 'Content-Type': 'application/json' },
|
|
300
|
-
body: JSON.stringify({ threshold, days })
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
if (!res.ok) {
|
|
304
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
305
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return res.json();
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Generate a streak proof
|
|
313
|
-
* Proves "N+ consecutive periods at >= threshold"
|
|
314
|
-
* @param {string} agentId - Agent ID
|
|
315
|
-
* @param {object} options - Proof options
|
|
316
|
-
* @param {number} [options.threshold=60] - Minimum score threshold
|
|
317
|
-
* @param {number} [options.minStreak=7] - Minimum consecutive periods
|
|
318
|
-
* @returns {Promise<StreakProof>}
|
|
319
|
-
*/
|
|
320
|
-
async generateStreakProof(agentId, options = {}) {
|
|
321
|
-
const { threshold = 60, minStreak = 7 } = options;
|
|
322
|
-
|
|
323
|
-
const res = await fetch(`${this.baseUrl}/api/stark/streak/${encodeURIComponent(agentId)}`, {
|
|
324
|
-
method: 'POST',
|
|
325
|
-
headers: { 'Content-Type': 'application/json' },
|
|
326
|
-
body: JSON.stringify({ threshold, minStreak })
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
if (!res.ok) {
|
|
330
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
331
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return res.json();
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Generate a stability proof
|
|
339
|
-
* Proves "score variance <= threshold" without revealing actual variance
|
|
340
|
-
* @param {string} agentId - Agent ID
|
|
341
|
-
* @param {object} options - Proof options
|
|
342
|
-
* @param {number} [options.maxVariance=100] - Maximum allowed variance
|
|
343
|
-
* @returns {Promise<StabilityProof>}
|
|
344
|
-
*/
|
|
345
|
-
async generateStabilityProof(agentId, options = {}) {
|
|
346
|
-
const { maxVariance = 100 } = options;
|
|
347
|
-
|
|
348
|
-
const res = await fetch(`${this.baseUrl}/api/stark/stability/${encodeURIComponent(agentId)}`, {
|
|
349
|
-
method: 'POST',
|
|
350
|
-
headers: { 'Content-Type': 'application/json' },
|
|
351
|
-
body: JSON.stringify({ maxVariance })
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
if (!res.ok) {
|
|
355
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
356
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return res.json();
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// ==========================================
|
|
363
|
-
// EXECUTION TRACES (Behavioral Scoring)
|
|
364
|
-
// ==========================================
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Submit an execution trace for behavioral scoring
|
|
368
|
-
* @param {string} agentId - Agent ID
|
|
369
|
-
* @param {TraceData} data - Trace data
|
|
370
|
-
* @returns {Promise<TraceResult>}
|
|
371
|
-
*/
|
|
372
|
-
async submitTrace(agentId, data) {
|
|
373
|
-
const res = await fetch(`${this.baseUrl}/api/traces`, {
|
|
374
|
-
method: 'POST',
|
|
375
|
-
headers: {
|
|
376
|
-
'Content-Type': 'application/json',
|
|
377
|
-
...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` })
|
|
378
|
-
},
|
|
379
|
-
body: JSON.stringify({ agentId, ...data })
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
if (!res.ok) {
|
|
383
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
384
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return res.json();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Get traces for an agent
|
|
392
|
-
* @param {string} agentId - Agent ID
|
|
393
|
-
* @param {object} [options] - Query options
|
|
394
|
-
* @returns {Promise<TraceList>}
|
|
395
|
-
*/
|
|
396
|
-
async getTraces(agentId, options = {}) {
|
|
397
|
-
const { limit = 20 } = options;
|
|
398
|
-
const res = await fetch(`${this.baseUrl}/api/traces/${encodeURIComponent(agentId)}?limit=${limit}`);
|
|
399
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
400
|
-
return res.json();
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Get behavioral score from traces
|
|
405
|
-
* @param {string} agentId - Agent ID
|
|
406
|
-
* @returns {Promise<BehavioralScore>}
|
|
407
|
-
*/
|
|
408
|
-
async getBehavioralScore(agentId) {
|
|
409
|
-
const res = await fetch(`${this.baseUrl}/api/traces/${encodeURIComponent(agentId)}/score`);
|
|
410
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
411
|
-
return res.json();
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Anchor a trace on-chain (requires SlotScribe integration)
|
|
416
|
-
* @param {string} traceId - Trace ID
|
|
417
|
-
* @returns {Promise<AnchorResult>}
|
|
418
|
-
*/
|
|
419
|
-
async anchorTrace(traceId) {
|
|
420
|
-
const res = await fetch(`${this.baseUrl}/api/traces/${encodeURIComponent(traceId)}/anchor`, {
|
|
421
|
-
method: 'POST',
|
|
422
|
-
headers: {
|
|
423
|
-
'Content-Type': 'application/json',
|
|
424
|
-
...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` })
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
if (!res.ok) {
|
|
429
|
-
const error = await res.json().catch(() => ({ error: res.statusText }));
|
|
430
|
-
throw new Error(error.error || `API error: ${res.status}`);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return res.json();
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// ==========================================
|
|
437
|
-
// HELPER METHODS
|
|
438
|
-
// ==========================================
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Quick check if an agent is verified
|
|
442
|
-
* @param {string} agentId - Agent ID
|
|
443
|
-
* @returns {Promise<boolean>}
|
|
444
|
-
*/
|
|
445
|
-
async isVerified(agentId) {
|
|
446
|
-
try {
|
|
447
|
-
const status = await this.getStatus(agentId);
|
|
448
|
-
return status.verified === true && status.score >= 60;
|
|
449
|
-
} catch {
|
|
450
|
-
return false;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Check if an agent has a specific capability at a minimum score
|
|
456
|
-
* @param {string} agentId - Agent ID
|
|
457
|
-
* @param {string} capability - Capability to check (e.g., "trading", "escrow")
|
|
458
|
-
* @param {number} [minScore=60] - Minimum score required
|
|
459
|
-
* @returns {Promise<boolean>}
|
|
460
|
-
*/
|
|
461
|
-
async checkCapability(agentId, capability, minScore = 60) {
|
|
462
|
-
try {
|
|
463
|
-
const status = await this.getStatus(agentId);
|
|
464
|
-
if (!status.verified || status.score < minScore) return false;
|
|
465
|
-
if (!status.capabilities) return status.score >= minScore;
|
|
466
|
-
return status.capabilities.includes(capability) && status.score >= minScore;
|
|
467
|
-
} catch {
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Get proof generation cost estimate
|
|
474
|
-
* @param {string} proofType - Type of proof (threshold, consistency, streak, stability)
|
|
475
|
-
* @returns {Promise<CostEstimate>}
|
|
476
|
-
*/
|
|
477
|
-
async getProofCost(proofType = 'threshold') {
|
|
478
|
-
// Costs are near-zero for these lightweight proofs
|
|
479
|
-
const costs = {
|
|
480
|
-
threshold: { computeMs: 50, estimatedCost: '$0.001' },
|
|
481
|
-
consistency: { computeMs: 120, estimatedCost: '$0.002' },
|
|
482
|
-
streak: { computeMs: 100, estimatedCost: '$0.002' },
|
|
483
|
-
stability: { computeMs: 80, estimatedCost: '$0.001' }
|
|
484
|
-
};
|
|
485
|
-
return costs[proofType] || costs.threshold;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// ==========================================
|
|
489
|
-
// HARDWARE-ANCHORED IDENTITY (Anti-Sybil)
|
|
490
|
-
// DePIN-Rooted Device Identity
|
|
491
|
-
// ==========================================
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Collect environment fingerprint for hardware-anchored identity
|
|
495
|
-
* @returns {object} Raw fingerprint data
|
|
496
|
-
* @private
|
|
497
|
-
*/
|
|
498
|
-
_collectFingerprint() {
|
|
499
|
-
const crypto = require('crypto');
|
|
500
|
-
const os = require('os');
|
|
501
|
-
|
|
502
|
-
const hardware = {
|
|
503
|
-
platform: os.platform(),
|
|
504
|
-
arch: os.arch(),
|
|
505
|
-
cpus: os.cpus().length,
|
|
506
|
-
cpuModel: os.cpus()[0]?.model || 'unknown',
|
|
507
|
-
totalMemory: os.totalmem(),
|
|
508
|
-
hostname: os.hostname(),
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
const runtime = {
|
|
512
|
-
nodeVersion: process.version,
|
|
513
|
-
pid: process.pid,
|
|
514
|
-
execPath: process.execPath,
|
|
515
|
-
cwd: process.cwd(),
|
|
516
|
-
env: {
|
|
517
|
-
USER: process.env.USER || process.env.USERNAME || 'unknown',
|
|
518
|
-
HOME: process.env.HOME || process.env.USERPROFILE || 'unknown',
|
|
519
|
-
SHELL: process.env.SHELL || 'unknown',
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
// Try to get network interfaces for fingerprinting
|
|
524
|
-
const nets = os.networkInterfaces();
|
|
525
|
-
const networkFingerprint = Object.keys(nets).sort().map(name => {
|
|
526
|
-
const iface = nets[name].find(n => !n.internal && n.family === 'IPv4');
|
|
527
|
-
return iface ? `${name}:${iface.mac}` : null;
|
|
528
|
-
}).filter(Boolean).join('|');
|
|
529
|
-
|
|
530
|
-
return { hardware, runtime, networkFingerprint };
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Try to read TPM endorsement key hash for hardware-rooted identity
|
|
535
|
-
* @returns {string|null} SHA-256 hash of TPM data, or null if unavailable
|
|
536
|
-
* @private
|
|
537
|
-
*/
|
|
538
|
-
_getTPMFingerprint() {
|
|
539
|
-
const crypto = require('crypto');
|
|
540
|
-
const fs = require('fs');
|
|
541
|
-
const os = require('os');
|
|
542
|
-
|
|
543
|
-
// TPM 2.0 paths (Linux)
|
|
544
|
-
const tpmPaths = [
|
|
545
|
-
'/sys/class/tpm/tpm0/device/description',
|
|
546
|
-
'/sys/class/tpm/tpm0/tpm_version_major',
|
|
547
|
-
'/sys/class/dmi/id/board_serial',
|
|
548
|
-
'/sys/class/dmi/id/product_uuid',
|
|
549
|
-
'/sys/class/dmi/id/chassis_serial',
|
|
550
|
-
];
|
|
551
|
-
|
|
552
|
-
const tpmData = [];
|
|
553
|
-
for (const p of tpmPaths) {
|
|
554
|
-
try {
|
|
555
|
-
const data = fs.readFileSync(p, 'utf-8').trim();
|
|
556
|
-
if (data && data !== 'None' && data !== '') {
|
|
557
|
-
tpmData.push(data);
|
|
558
|
-
}
|
|
559
|
-
} catch {}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// macOS: use IOPlatformUUID
|
|
563
|
-
if (os.platform() === 'darwin') {
|
|
564
|
-
try {
|
|
565
|
-
const { execSync } = require('child_process');
|
|
566
|
-
const uuid = execSync('ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID', { encoding: 'utf-8' });
|
|
567
|
-
const match = uuid.match(/"([A-F0-9-]+)"/);
|
|
568
|
-
if (match) tpmData.push(match[1]);
|
|
569
|
-
} catch {}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (tpmData.length === 0) return null;
|
|
573
|
-
|
|
574
|
-
return crypto.createHash('sha256')
|
|
575
|
-
.update(tpmData.join('|'))
|
|
576
|
-
.digest('hex');
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Collect REAL TPM attestation with challenge-response
|
|
581
|
-
* Uses system-level tools (tpm2-tools, ioreg, machine-id) for cryptographic attestation
|
|
582
|
-
* Challenge is mixed into evidence hash to prevent replay attacks
|
|
583
|
-
*
|
|
584
|
-
* @param {string} challenge - Server-issued challenge nonce
|
|
585
|
-
* @returns {object} Attestation result with evidence, method, and availability
|
|
586
|
-
* @private
|
|
587
|
-
*/
|
|
588
|
-
_getTPMAttestation(challenge) {
|
|
589
|
-
const { execSync } = require('child_process');
|
|
590
|
-
const crypto = require('crypto');
|
|
591
|
-
const os = require('os');
|
|
592
|
-
|
|
593
|
-
const attestation = {
|
|
594
|
-
available: false,
|
|
595
|
-
method: null,
|
|
596
|
-
evidence: null,
|
|
597
|
-
challenge: challenge
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
// Method 1: tpm2-tools (Linux with TPM 2.0)
|
|
601
|
-
try {
|
|
602
|
-
const tpmVersion = execSync('cat /sys/class/tpm/tpm0/tpm_version_major 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
603
|
-
|
|
604
|
-
if (tpmVersion === '2') {
|
|
605
|
-
// Read PCR values (reflect boot chain — can't be faked without rebooting)
|
|
606
|
-
const pcrValues = execSync('tpm2_pcrread sha256:0,1,2,3,4,5,6,7 2>/dev/null || echo "unavailable"', { encoding: 'utf-8' }).trim();
|
|
607
|
-
|
|
608
|
-
// Try to get EK certificate (endorsement key — burned at manufacture)
|
|
609
|
-
const ekCert = execSync('tpm2_getekcertificate 2>/dev/null || tpm2_nvread 0x01c00002 2>/dev/null || echo "unavailable"', { encoding: 'utf-8' }).trim();
|
|
610
|
-
|
|
611
|
-
// Read platform info that's hardware-bound
|
|
612
|
-
const platformInfo = [];
|
|
613
|
-
for (const p of ['/sys/class/dmi/id/board_serial', '/sys/class/dmi/id/product_uuid', '/sys/class/dmi/id/chassis_serial']) {
|
|
614
|
-
try {
|
|
615
|
-
const val = require('fs').readFileSync(p, 'utf-8').trim();
|
|
616
|
-
if (val && val !== 'None' && val !== 'Not Specified' && val !== '') {
|
|
617
|
-
platformInfo.push(val);
|
|
618
|
-
}
|
|
619
|
-
} catch {}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (pcrValues !== 'unavailable' || platformInfo.length > 0) {
|
|
623
|
-
// Hash attestation evidence WITH the challenge (prevents replay)
|
|
624
|
-
const evidence = crypto.createHash('sha256')
|
|
625
|
-
.update(challenge)
|
|
626
|
-
.update(pcrValues)
|
|
627
|
-
.update(platformInfo.join('|'))
|
|
628
|
-
.update(ekCert !== 'unavailable' ? ekCert : '')
|
|
629
|
-
.digest('hex');
|
|
630
|
-
|
|
631
|
-
attestation.available = true;
|
|
632
|
-
attestation.method = 'tpm2';
|
|
633
|
-
attestation.evidence = evidence;
|
|
634
|
-
attestation.pcrAvailable = pcrValues !== 'unavailable';
|
|
635
|
-
attestation.ekAvailable = ekCert !== 'unavailable';
|
|
636
|
-
attestation.platformFields = platformInfo.length;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
} catch {}
|
|
640
|
-
|
|
641
|
-
// Method 2: macOS Secure Enclave / IOPlatformUUID
|
|
642
|
-
if (!attestation.available && os.platform() === 'darwin') {
|
|
643
|
-
try {
|
|
644
|
-
const uuid = execSync('ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID', { encoding: 'utf-8' });
|
|
645
|
-
const match = uuid.match(/"([A-F0-9-]+)"/);
|
|
646
|
-
if (match) {
|
|
647
|
-
const evidence = crypto.createHash('sha256')
|
|
648
|
-
.update(challenge)
|
|
649
|
-
.update(match[1])
|
|
650
|
-
.digest('hex');
|
|
651
|
-
|
|
652
|
-
attestation.available = true;
|
|
653
|
-
attestation.method = 'macos-platform-uuid';
|
|
654
|
-
attestation.evidence = evidence;
|
|
655
|
-
}
|
|
656
|
-
} catch {}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Method 3: Linux machine-id (weaker but always available on Linux)
|
|
660
|
-
if (!attestation.available && os.platform() === 'linux') {
|
|
661
|
-
try {
|
|
662
|
-
const machineId = require('fs').readFileSync('/etc/machine-id', 'utf-8').trim();
|
|
663
|
-
const evidence = crypto.createHash('sha256')
|
|
664
|
-
.update(challenge)
|
|
665
|
-
.update(machineId)
|
|
666
|
-
.digest('hex');
|
|
667
|
-
|
|
668
|
-
attestation.available = true;
|
|
669
|
-
attestation.method = 'linux-machine-id';
|
|
670
|
-
attestation.evidence = evidence;
|
|
671
|
-
attestation.note = 'machine-id is persistent but root-changeable';
|
|
672
|
-
} catch {}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
return attestation;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* Perform full TPM challenge-response verification against MoltLaunch server
|
|
680
|
-
* 1. Request challenge from server
|
|
681
|
-
* 2. Collect local TPM attestation with that challenge
|
|
682
|
-
* 3. Submit attestation to server for verification
|
|
683
|
-
*
|
|
684
|
-
* @param {string} agentId - Agent ID to verify TPM for
|
|
685
|
-
* @returns {Promise<TPMVerifyResult>}
|
|
686
|
-
*/
|
|
687
|
-
async verifyTPM(agentId) {
|
|
688
|
-
// 1. Get challenge from server
|
|
689
|
-
const challengeRes = await fetch(`${this.baseUrl}/api/identity/tpm/challenge`, {
|
|
690
|
-
method: 'POST',
|
|
691
|
-
headers: { 'Content-Type': 'application/json' },
|
|
692
|
-
body: JSON.stringify({ agentId })
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
if (!challengeRes.ok) {
|
|
696
|
-
const err = await challengeRes.json().catch(() => ({ error: challengeRes.statusText }));
|
|
697
|
-
throw new Error(err.error || `Challenge request failed: ${challengeRes.status}`);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const { challenge } = await challengeRes.json();
|
|
701
|
-
|
|
702
|
-
// 2. Collect local attestation
|
|
703
|
-
const attestation = this._getTPMAttestation(challenge);
|
|
704
|
-
|
|
705
|
-
if (!attestation.available) {
|
|
706
|
-
return { verified: false, reason: 'TPM not available on this machine' };
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// 3. Submit to server for verification
|
|
710
|
-
const verifyRes = await fetch(`${this.baseUrl}/api/identity/tpm/verify`, {
|
|
711
|
-
method: 'POST',
|
|
712
|
-
headers: { 'Content-Type': 'application/json' },
|
|
713
|
-
body: JSON.stringify({ agentId, attestation })
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
if (!verifyRes.ok) {
|
|
717
|
-
const err = await verifyRes.json().catch(() => ({ error: verifyRes.statusText }));
|
|
718
|
-
throw new Error(err.error || `TPM verify failed: ${verifyRes.status}`);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
return verifyRes.json();
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Register a DePIN device attestation for hardware-rooted identity
|
|
726
|
-
* Links agent identity to a physically verified DePIN device
|
|
727
|
-
* If devicePDA is provided, the server verifies the account exists on Solana
|
|
728
|
-
*
|
|
729
|
-
* @param {object} options - DePIN registration options
|
|
730
|
-
* @param {string} options.provider - DePIN provider name (e.g., 'io.net', 'akash', 'render')
|
|
731
|
-
* @param {string} options.deviceId - Device ID from the DePIN provider
|
|
732
|
-
* @param {string} [options.devicePDA] - Solana PDA address for the device (enables on-chain verification)
|
|
733
|
-
* @param {string} [options.attestation] - Optional attestation data from the provider
|
|
734
|
-
* @param {string} options.agentId - Agent ID to bind DePIN identity to
|
|
735
|
-
* @returns {Promise<DePINRegistrationResult>}
|
|
736
|
-
*/
|
|
737
|
-
async registerDePINDevice(options = {}) {
|
|
738
|
-
const { provider, deviceId, devicePDA, attestation, agentId } = options;
|
|
739
|
-
|
|
740
|
-
const supported = ['io.net', 'akash', 'render', 'helium', 'hivemapper', 'nosana'];
|
|
741
|
-
|
|
742
|
-
if (!supported.includes(provider)) {
|
|
743
|
-
throw new Error(`Unsupported DePIN provider. Supported: ${supported.join(', ')}`);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
const res = await fetch(`${this.baseUrl}/api/identity/depin`, {
|
|
747
|
-
method: 'POST',
|
|
748
|
-
headers: { 'Content-Type': 'application/json' },
|
|
749
|
-
body: JSON.stringify({
|
|
750
|
-
agentId,
|
|
751
|
-
depinProvider: provider,
|
|
752
|
-
deviceId,
|
|
753
|
-
devicePDA: devicePDA || null,
|
|
754
|
-
attestation,
|
|
755
|
-
timestamp: Date.now()
|
|
756
|
-
})
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
if (!res.ok) throw new Error(`DePIN registration failed: ${res.status}`);
|
|
760
|
-
return res.json();
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
* Get identity trust report for an agent
|
|
765
|
-
* Shows trust ladder breakdown including DePIN and TPM attestation levels
|
|
766
|
-
*
|
|
767
|
-
* @param {string} agentId - Agent ID to get report for
|
|
768
|
-
* @returns {Promise<IdentityReport>}
|
|
769
|
-
*/
|
|
770
|
-
async getIdentityReport(agentId) {
|
|
771
|
-
const res = await fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId)}/report`);
|
|
772
|
-
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
773
|
-
return res.json();
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
* Generate a hardware-anchored identity hash
|
|
778
|
-
* Combines hardware, runtime, code, and network fingerprints into a deterministic identity
|
|
779
|
-
*
|
|
780
|
-
* @param {object} options - Identity options
|
|
781
|
-
* @param {boolean} [options.includeHardware=true] - Include hardware fingerprint (CPU, memory)
|
|
782
|
-
* @param {boolean} [options.includeRuntime=true] - Include runtime fingerprint (Node version, OS)
|
|
783
|
-
* @param {boolean} [options.includeCode=false] - Include code hash (hash of main module)
|
|
784
|
-
* @param {string} [options.codeEntry] - Path to agent's main module for code hashing
|
|
785
|
-
* @param {string} [options.agentId] - Agent ID to bind identity to
|
|
786
|
-
* @param {boolean} [options.anchor=false] - Anchor identity on Solana
|
|
787
|
-
* @returns {Promise<IdentityResult>}
|
|
788
|
-
*/
|
|
789
|
-
async generateIdentity(options = {}) {
|
|
790
|
-
const crypto = require('crypto');
|
|
791
|
-
const {
|
|
792
|
-
includeHardware = true,
|
|
793
|
-
includeRuntime = true,
|
|
794
|
-
includeCode = false,
|
|
795
|
-
includeTPM = false,
|
|
796
|
-
depinProvider,
|
|
797
|
-
depinDeviceId,
|
|
798
|
-
codeEntry,
|
|
799
|
-
agentId,
|
|
800
|
-
anchor = false
|
|
801
|
-
} = options;
|
|
802
|
-
|
|
803
|
-
const fingerprint = this._collectFingerprint();
|
|
804
|
-
const components = [];
|
|
805
|
-
|
|
806
|
-
if (includeHardware) {
|
|
807
|
-
const hwHash = crypto.createHash('sha256')
|
|
808
|
-
.update(JSON.stringify(fingerprint.hardware))
|
|
809
|
-
.digest('hex');
|
|
810
|
-
components.push(`hw:${hwHash}`);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
if (includeRuntime) {
|
|
814
|
-
const rtHash = crypto.createHash('sha256')
|
|
815
|
-
.update(JSON.stringify(fingerprint.runtime))
|
|
816
|
-
.digest('hex');
|
|
817
|
-
components.push(`rt:${rtHash}`);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
if (includeCode && codeEntry) {
|
|
821
|
-
try {
|
|
822
|
-
const fs = require('fs');
|
|
823
|
-
const codeContent = fs.readFileSync(codeEntry, 'utf-8');
|
|
824
|
-
const codeHash = crypto.createHash('sha256')
|
|
825
|
-
.update(codeContent)
|
|
826
|
-
.digest('hex');
|
|
827
|
-
components.push(`code:${codeHash}`);
|
|
828
|
-
} catch (e) {
|
|
829
|
-
components.push(`code:unavailable`);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
if (fingerprint.networkFingerprint) {
|
|
834
|
-
const netHash = crypto.createHash('sha256')
|
|
835
|
-
.update(fingerprint.networkFingerprint)
|
|
836
|
-
.digest('hex');
|
|
837
|
-
components.push(`net:${netHash}`);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// TPM attestation (hardware-rooted identity - trust level 4)
|
|
841
|
-
// Uses challenge-response: requests challenge from server, attests locally, verifies on server
|
|
842
|
-
let tpmHash = null;
|
|
843
|
-
let tpmAttestation = null;
|
|
844
|
-
if (includeTPM) {
|
|
845
|
-
// Legacy fallback: static fingerprint (no challenge-response)
|
|
846
|
-
tpmHash = this._getTPMFingerprint();
|
|
847
|
-
if (tpmHash) {
|
|
848
|
-
components.push(`tpm:${tpmHash}`);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// Real challenge-response TPM attestation (if server is reachable)
|
|
852
|
-
if (agentId) {
|
|
853
|
-
try {
|
|
854
|
-
const tpmResult = await this.verifyTPM(agentId);
|
|
855
|
-
if (tpmResult.verified || tpmResult.success) {
|
|
856
|
-
tpmAttestation = {
|
|
857
|
-
method: tpmResult.tpmMethod,
|
|
858
|
-
verified: true,
|
|
859
|
-
trustLevel: tpmResult.trustLevel
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
} catch (e) {
|
|
863
|
-
// Server unreachable or TPM not available — fall back to static fingerprint
|
|
864
|
-
tpmAttestation = { verified: false, error: e.message };
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// DePIN device attestation (highest trust level 5)
|
|
870
|
-
if (depinProvider && depinDeviceId) {
|
|
871
|
-
const depinHash = crypto.createHash('sha256')
|
|
872
|
-
.update(`depin:${depinProvider}:${depinDeviceId}`)
|
|
873
|
-
.digest('hex');
|
|
874
|
-
components.push(`depin:${depinHash}`);
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// Generate deterministic identity hash
|
|
878
|
-
const identityHash = crypto.createHash('sha256')
|
|
879
|
-
.update(components.join('|'))
|
|
880
|
-
.digest('hex');
|
|
881
|
-
|
|
882
|
-
const identity = {
|
|
883
|
-
hash: identityHash,
|
|
884
|
-
components: components.length,
|
|
885
|
-
includesHardware: includeHardware,
|
|
886
|
-
includesRuntime: includeRuntime,
|
|
887
|
-
includesCode: includeCode && !!codeEntry,
|
|
888
|
-
includesNetwork: !!fingerprint.networkFingerprint,
|
|
889
|
-
includesTPM: includeTPM && !!tpmHash,
|
|
890
|
-
tpmHash: tpmHash || null,
|
|
891
|
-
tpmAttestation: tpmAttestation || null,
|
|
892
|
-
depinProvider: depinProvider || null,
|
|
893
|
-
depinDeviceId: depinDeviceId || null,
|
|
894
|
-
generatedAt: new Date().toISOString(),
|
|
895
|
-
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days
|
|
896
|
-
agentId: agentId || null
|
|
897
|
-
};
|
|
898
|
-
|
|
899
|
-
// Register with MoltLaunch API
|
|
900
|
-
if (agentId) {
|
|
901
|
-
try {
|
|
902
|
-
const res = await fetch(`${this.baseUrl}/api/identity/register`, {
|
|
903
|
-
method: 'POST',
|
|
904
|
-
headers: {
|
|
905
|
-
'Content-Type': 'application/json',
|
|
906
|
-
...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` })
|
|
907
|
-
},
|
|
908
|
-
body: JSON.stringify({
|
|
909
|
-
agentId,
|
|
910
|
-
identityHash,
|
|
911
|
-
components: components.length,
|
|
912
|
-
includesHardware: includeHardware,
|
|
913
|
-
includesCode: includeCode
|
|
914
|
-
})
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
if (res.ok) {
|
|
918
|
-
const registration = await res.json();
|
|
919
|
-
identity.registered = true;
|
|
920
|
-
identity.registrationId = registration.registrationId;
|
|
921
|
-
} else {
|
|
922
|
-
identity.registered = false;
|
|
923
|
-
identity.registrationError = `API ${res.status}`;
|
|
924
|
-
}
|
|
925
|
-
} catch (e) {
|
|
926
|
-
identity.registered = false;
|
|
927
|
-
identity.registrationError = e.message;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
// Anchor on Solana if requested
|
|
932
|
-
if (anchor && agentId) {
|
|
933
|
-
try {
|
|
934
|
-
const res = await fetch(`${this.baseUrl}/api/anchor/verification`, {
|
|
935
|
-
method: 'POST',
|
|
936
|
-
headers: { 'Content-Type': 'application/json' },
|
|
937
|
-
body: JSON.stringify({
|
|
938
|
-
agentId,
|
|
939
|
-
attestationHash: identityHash
|
|
940
|
-
})
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
if (res.ok) {
|
|
944
|
-
const anchorResult = await res.json();
|
|
945
|
-
identity.anchored = true;
|
|
946
|
-
identity.anchorSignature = anchorResult.signature;
|
|
947
|
-
identity.anchorExplorer = anchorResult.explorer;
|
|
948
|
-
} else {
|
|
949
|
-
identity.anchored = false;
|
|
950
|
-
}
|
|
951
|
-
} catch (e) {
|
|
952
|
-
identity.anchored = false;
|
|
953
|
-
identity.anchorError = e.message;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
return identity;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/**
|
|
961
|
-
* Verify an agent's identity against their registered fingerprint
|
|
962
|
-
* @param {string} agentId - Agent ID to verify
|
|
963
|
-
* @returns {Promise<IdentityVerification>}
|
|
964
|
-
*/
|
|
965
|
-
async verifyIdentity(agentId) {
|
|
966
|
-
// Generate current fingerprint
|
|
967
|
-
const currentIdentity = await this.generateIdentity({ agentId });
|
|
968
|
-
|
|
969
|
-
// Check against registered identity
|
|
970
|
-
try {
|
|
971
|
-
const res = await fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId)}`);
|
|
972
|
-
if (!res.ok) {
|
|
973
|
-
return {
|
|
974
|
-
valid: false,
|
|
975
|
-
agentId,
|
|
976
|
-
reason: 'No registered identity found',
|
|
977
|
-
currentHash: currentIdentity.hash
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
const registered = await res.json();
|
|
982
|
-
const matches = registered.identityHash === currentIdentity.hash;
|
|
983
|
-
|
|
984
|
-
return {
|
|
985
|
-
valid: matches,
|
|
986
|
-
agentId,
|
|
987
|
-
currentHash: currentIdentity.hash,
|
|
988
|
-
registeredHash: registered.identityHash,
|
|
989
|
-
match: matches,
|
|
990
|
-
registeredAt: registered.registeredAt,
|
|
991
|
-
reason: matches ? 'Identity confirmed' : 'Identity mismatch — different hardware or code'
|
|
992
|
-
};
|
|
993
|
-
} catch (e) {
|
|
994
|
-
return {
|
|
995
|
-
valid: false,
|
|
996
|
-
agentId,
|
|
997
|
-
reason: e.message,
|
|
998
|
-
currentHash: currentIdentity.hash
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
/**
|
|
1004
|
-
* Check if two agents have the same hardware fingerprint (Sybil detection)
|
|
1005
|
-
* @param {string} agentId1 - First agent
|
|
1006
|
-
* @param {string} agentId2 - Second agent
|
|
1007
|
-
* @returns {Promise<SybilCheck>}
|
|
1008
|
-
*/
|
|
1009
|
-
async checkSybil(agentId1, agentId2) {
|
|
1010
|
-
try {
|
|
1011
|
-
const [id1, id2] = await Promise.all([
|
|
1012
|
-
fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId1)}`).then(r => r.json()),
|
|
1013
|
-
fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId2)}`).then(r => r.json())
|
|
1014
|
-
]);
|
|
1015
|
-
|
|
1016
|
-
const sameIdentity = id1.identityHash === id2.identityHash;
|
|
1017
|
-
|
|
1018
|
-
return {
|
|
1019
|
-
agentId1,
|
|
1020
|
-
agentId2,
|
|
1021
|
-
sameIdentity,
|
|
1022
|
-
sybilRisk: sameIdentity ? 'HIGH' : 'LOW',
|
|
1023
|
-
reason: sameIdentity
|
|
1024
|
-
? 'Same hardware fingerprint — likely same operator'
|
|
1025
|
-
: 'Different hardware fingerprints — likely different operators',
|
|
1026
|
-
recommendation: sameIdentity
|
|
1027
|
-
? 'Do not seat at same table'
|
|
1028
|
-
: 'Safe to interact'
|
|
1029
|
-
};
|
|
1030
|
-
} catch (e) {
|
|
1031
|
-
return {
|
|
1032
|
-
agentId1,
|
|
1033
|
-
agentId2,
|
|
1034
|
-
sameIdentity: null,
|
|
1035
|
-
sybilRisk: 'UNKNOWN',
|
|
1036
|
-
reason: `Could not compare: ${e.message}`
|
|
1037
|
-
};
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
/**
|
|
1042
|
-
* Check a list of agents for Sybil clusters (table seating check)
|
|
1043
|
-
* @param {string[]} agentIds - List of agent IDs to check
|
|
1044
|
-
* @returns {Promise<SybilTableCheck>}
|
|
1045
|
-
*/
|
|
1046
|
-
async checkTableSybils(agentIds) {
|
|
1047
|
-
// Fetch all identities
|
|
1048
|
-
const identities = {};
|
|
1049
|
-
for (const id of agentIds) {
|
|
1050
|
-
try {
|
|
1051
|
-
const res = await fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(id)}`);
|
|
1052
|
-
if (res.ok) {
|
|
1053
|
-
const data = await res.json();
|
|
1054
|
-
identities[id] = data.identityHash;
|
|
1055
|
-
}
|
|
1056
|
-
} catch {
|
|
1057
|
-
// Skip agents without identity
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
// Find clusters (same identity hash)
|
|
1062
|
-
const hashToAgents = {};
|
|
1063
|
-
for (const [agentId, hash] of Object.entries(identities)) {
|
|
1064
|
-
if (!hashToAgents[hash]) hashToAgents[hash] = [];
|
|
1065
|
-
hashToAgents[hash].push(agentId);
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
const clusters = Object.values(hashToAgents).filter(group => group.length > 1);
|
|
1069
|
-
const flagged = clusters.flat();
|
|
1070
|
-
|
|
1071
|
-
return {
|
|
1072
|
-
totalAgents: agentIds.length,
|
|
1073
|
-
identifiedAgents: Object.keys(identities).length,
|
|
1074
|
-
unidentifiedAgents: agentIds.filter(id => !identities[id]),
|
|
1075
|
-
sybilClusters: clusters,
|
|
1076
|
-
flaggedAgents: flagged,
|
|
1077
|
-
safe: clusters.length === 0,
|
|
1078
|
-
recommendation: clusters.length === 0
|
|
1079
|
-
? 'No Sybil clusters detected — safe to proceed'
|
|
1080
|
-
: `${clusters.length} Sybil cluster(s) detected — ${flagged.length} agents share hardware`
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// Scoring helpers
|
|
1086
|
-
const SCORE_TIERS = {
|
|
1087
|
-
excellent: { min: 80, max: 100, label: 'Production Ready' },
|
|
1088
|
-
good: { min: 60, max: 79, label: 'Verified' },
|
|
1089
|
-
needs_work: { min: 40, max: 59, label: 'Needs Improvement' },
|
|
1090
|
-
poor: { min: 0, max: 39, label: 'Not Ready' }
|
|
1091
|
-
};
|
|
1092
|
-
|
|
1093
|
-
const getTier = (score) => {
|
|
1094
|
-
if (score >= 80) return 'excellent';
|
|
1095
|
-
if (score >= 60) return 'good';
|
|
1096
|
-
if (score >= 40) return 'needs_work';
|
|
1097
|
-
return 'poor';
|
|
1098
|
-
};
|
|
1099
|
-
|
|
1100
|
-
const isVerified = (score) => score >= 60;
|
|
1101
|
-
|
|
1102
|
-
// On-chain AI deployment info
|
|
1103
|
-
const DEPLOYMENT = {
|
|
1104
|
-
network: 'solana-devnet',
|
|
1105
|
-
vm: 'FHcy35f4NGZK9b6j5TGMYstfB6PXEtmNbMLvjfR1y2Li',
|
|
1106
|
-
weights: 'GnSxMWbZEa538vJ9Pf3veDrKP1LkzPiaaVmC4mRnM91N',
|
|
1107
|
-
program: 'FRsToriMLgDc1Ud53ngzHUZvCRoazCaGeGUuzkwoha7m'
|
|
1108
|
-
};
|
|
1109
|
-
|
|
1110
|
-
module.exports = {
|
|
1111
|
-
MoltLaunch,
|
|
1112
|
-
SCORE_TIERS,
|
|
1113
|
-
DEPLOYMENT,
|
|
1114
|
-
getTier,
|
|
1115
|
-
isVerified,
|
|
1116
|
-
DEFAULT_BASE_URL
|
|
1117
|
-
};
|