@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/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
- };