@moltlaunch/sdk 2.0.0 → 2.2.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 CHANGED
@@ -235,6 +235,228 @@ app.post('/pool/join', async (req, res) => {
235
235
  });
236
236
  ```
237
237
 
238
+ ## STARK Proofs (v2.1+)
239
+
240
+ Privacy-preserving proofs that prove properties without revealing exact values.
241
+
242
+ ### generateProof(agentId, options)
243
+
244
+ Generate a threshold proof: proves "score >= X" without revealing exact score.
245
+
246
+ ```javascript
247
+ const proof = await ml.generateProof('my-agent', { threshold: 60 });
248
+
249
+ console.log(proof.valid); // true
250
+ console.log(proof.claim); // "Score >= 60"
251
+ console.log(proof.proof.commitment); // cryptographic commitment
252
+ // Verifier knows: agent passed 60
253
+ // Verifier doesn't know: actual score (could be 61 or 99)
254
+ ```
255
+
256
+ ### generateConsistencyProof(agentId, options)
257
+
258
+ Prove "maintained >= threshold for N periods" without revealing individual scores.
259
+
260
+ ```javascript
261
+ const proof = await ml.generateConsistencyProof('my-agent', {
262
+ threshold: 60,
263
+ days: 30
264
+ });
265
+
266
+ console.log(proof.periodCount); // 30
267
+ console.log(proof.timeRange); // { start: '...', end: '...' }
268
+ console.log(proof.valid); // true if ALL periods met threshold
269
+ ```
270
+
271
+ ### generateStreakProof(agentId, options)
272
+
273
+ Prove "N+ consecutive periods at >= threshold".
274
+
275
+ ```javascript
276
+ const proof = await ml.generateStreakProof('my-agent', {
277
+ threshold: 60,
278
+ minStreak: 7
279
+ });
280
+
281
+ // Proves agent maintained 7+ consecutive good periods
282
+ // Without revealing actual streak length
283
+ ```
284
+
285
+ ### generateStabilityProof(agentId, options)
286
+
287
+ Prove "score variance stayed below threshold".
288
+
289
+ ```javascript
290
+ const proof = await ml.generateStabilityProof('my-agent', {
291
+ maxVariance: 100
292
+ });
293
+
294
+ // Proves consistent performance without volatility
295
+ // Without revealing actual variance
296
+ ```
297
+
298
+ ### Proof Cost Estimate
299
+
300
+ ```javascript
301
+ const cost = await ml.getProofCost('consistency');
302
+ console.log(cost.computeMs); // 120
303
+ console.log(cost.estimatedCost); // '$0.002'
304
+ ```
305
+
306
+ ## Execution Traces (Behavioral Scoring)
307
+
308
+ Submit and query behavioral traces for continuous reputation.
309
+
310
+ ### submitTrace(agentId, data)
311
+
312
+ Submit execution data for behavioral scoring.
313
+
314
+ ```javascript
315
+ const trace = await ml.submitTrace('my-agent', {
316
+ period: {
317
+ start: '2026-02-01T00:00:00Z',
318
+ end: '2026-02-07T23:59:59Z'
319
+ },
320
+ summary: {
321
+ totalActions: 150,
322
+ successRate: 0.92,
323
+ errorRate: 0.03,
324
+ avgResponseTime: 120,
325
+ // Domain-specific metrics
326
+ tradesExecuted: 45,
327
+ winRate: 0.73
328
+ }
329
+ });
330
+
331
+ console.log(trace.traceId); // 'trace_abc123'
332
+ console.log(trace.commitment); // Merkle root
333
+ console.log(trace.behavioralScore); // +15 points
334
+ ```
335
+
336
+ ### getBehavioralScore(agentId)
337
+
338
+ Get current behavioral score from all traces.
339
+
340
+ ```javascript
341
+ const score = await ml.getBehavioralScore('my-agent');
342
+
343
+ console.log(score.score); // 22
344
+ console.log(score.breakdown); // { hasTraces: 5, verified: 5, history7d: 5, ... }
345
+ console.log(score.traceCount); // 12
346
+ ```
347
+
348
+ ### anchorTrace(traceId)
349
+
350
+ Anchor a trace commitment on-chain for tamper-proof audit.
351
+
352
+ ```javascript
353
+ const anchor = await ml.anchorTrace('trace_abc123');
354
+
355
+ console.log(anchor.anchored); // true
356
+ console.log(anchor.txSignature); // Solana transaction signature
357
+ console.log(anchor.slot); // 12345678
358
+ ```
359
+
360
+ ## Helper Methods
361
+
362
+ ### isVerified(agentId)
363
+
364
+ Quick boolean check.
365
+
366
+ ```javascript
367
+ if (await ml.isVerified('suspicious-agent')) {
368
+ allowAccess();
369
+ }
370
+ ```
371
+
372
+ ### checkCapability(agentId, capability, minScore)
373
+
374
+ Check if agent has a capability at a minimum score.
375
+
376
+ ```javascript
377
+ // Check if agent can handle escrow at score >= 70
378
+ const canEscrow = await ml.checkCapability('my-agent', 'escrow', 70);
379
+
380
+ if (canEscrow) {
381
+ // Allow high-value escrow transactions
382
+ }
383
+ ```
384
+
385
+ ## Integration Patterns
386
+
387
+ ### Pre-Transaction Verification (AgentChain)
388
+
389
+ ```javascript
390
+ const ml = new MoltLaunch();
391
+
392
+ async function beforeEscrow(agentId, amount) {
393
+ const status = await ml.getStatus(agentId);
394
+
395
+ if (!status.verified) {
396
+ throw new Error('Agent not verified');
397
+ }
398
+
399
+ // Tiered limits based on score
400
+ const limit = status.tier === 'excellent' ? 10000
401
+ : status.tier === 'good' ? 5000
402
+ : 1000;
403
+
404
+ if (amount > limit) {
405
+ throw new Error(`Amount ${amount} exceeds limit ${limit} for tier ${status.tier}`);
406
+ }
407
+
408
+ return true;
409
+ }
410
+ ```
411
+
412
+ ### Competitive Privacy (Trading Bots)
413
+
414
+ ```javascript
415
+ // Prove capability without revealing edge
416
+ const proof = await ml.generateProof('my-trading-bot', { threshold: 70 });
417
+
418
+ // Counterparty can verify you're "good enough"
419
+ // But can't see if you scored 71 or 95
420
+ console.log(proof.claim); // "Score >= 70"
421
+ ```
422
+
423
+ ### Consistency Requirements (Poker)
424
+
425
+ ```javascript
426
+ // Prove maintained performance over time
427
+ const consistency = await ml.generateConsistencyProof('poker-bot', {
428
+ threshold: 60,
429
+ days: 30
430
+ });
431
+
432
+ if (consistency.valid) {
433
+ // Bot has been reliable for 30 days
434
+ allowTableEntry();
435
+ }
436
+ ```
437
+
438
+ ## Changelog
439
+
440
+ ### v2.1.0
441
+ - Added `generateProof()` for threshold STARK proofs
442
+ - Added `generateConsistencyProof()` for time-series proofs
443
+ - Added `generateStreakProof()` for consecutive period proofs
444
+ - Added `generateStabilityProof()` for variance proofs
445
+ - Added `submitTrace()` for behavioral scoring
446
+ - Added `getBehavioralScore()` for trace-based reputation
447
+ - Added `anchorTrace()` for on-chain anchoring
448
+ - Added `isVerified()` helper
449
+ - Added `checkCapability()` for capability checks
450
+ - Added `getProofCost()` for cost estimates
451
+
452
+ ### v2.0.0
453
+ - On-chain AI verification via Cauldron
454
+ - Batch status checks
455
+ - Pool application API
456
+
457
+ ### v1.0.0
458
+ - Initial release
459
+
238
460
  ## License
239
461
 
240
462
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moltlaunch/sdk",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "MoltLaunch SDK - On-chain AI verification for AI agents",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
package/src/index.d.ts CHANGED
@@ -131,15 +131,128 @@ export declare const DEFAULT_BASE_URL: string;
131
131
  export declare function getTier(score: number): 'excellent' | 'good' | 'needs_work' | 'poor';
132
132
  export declare function isVerified(score: number): boolean;
133
133
 
134
+ // STARK Proof Types
135
+ export interface STARKProof {
136
+ agentId: string;
137
+ proofType: string;
138
+ claim: string;
139
+ valid: boolean;
140
+ proof: {
141
+ commitment: string;
142
+ proofHash: string;
143
+ generatedAt: string;
144
+ };
145
+ privacyNote: string;
146
+ }
147
+
148
+ export interface ConsistencyProof extends STARKProof {
149
+ periodCount: number;
150
+ timeRange: {
151
+ start: string;
152
+ end: string;
153
+ };
154
+ }
155
+
156
+ export interface StreakProof extends STARKProof {
157
+ minStreak: number;
158
+ }
159
+
160
+ export interface StabilityProof extends STARKProof {
161
+ periodCount: number;
162
+ }
163
+
164
+ // Trace Types
165
+ export interface TraceData {
166
+ period?: {
167
+ start: string;
168
+ end: string;
169
+ };
170
+ actions?: Array<{
171
+ type: string;
172
+ timestamp: string;
173
+ success: boolean;
174
+ metadata?: Record<string, any>;
175
+ }>;
176
+ summary?: Record<string, any>;
177
+ }
178
+
179
+ export interface TraceResult {
180
+ traceId: string;
181
+ agentId: string;
182
+ commitment: string;
183
+ behavioralScore?: number;
184
+ createdAt: string;
185
+ }
186
+
187
+ export interface BehavioralScore {
188
+ agentId: string;
189
+ score: number;
190
+ breakdown: Record<string, number>;
191
+ traceCount: number;
192
+ calculatedAt: string;
193
+ }
194
+
195
+ export interface AnchorResult {
196
+ traceId: string;
197
+ anchored: boolean;
198
+ txSignature?: string;
199
+ slot?: number;
200
+ }
201
+
202
+ export interface CostEstimate {
203
+ computeMs: number;
204
+ estimatedCost: string;
205
+ }
206
+
207
+ export interface ProofOptions {
208
+ threshold?: number;
209
+ }
210
+
211
+ export interface ConsistencyProofOptions extends ProofOptions {
212
+ days?: number;
213
+ }
214
+
215
+ export interface StreakProofOptions extends ProofOptions {
216
+ minStreak?: number;
217
+ }
218
+
219
+ export interface StabilityProofOptions {
220
+ maxVariance?: number;
221
+ }
222
+
134
223
  export declare class MoltLaunch {
135
224
  constructor(options?: MoltLaunchOptions);
136
225
 
226
+ // Core verification
137
227
  getOnChainInfo(): Promise<OnChainInfo>;
138
228
  verify(options: VerifyOptions): Promise<VerificationResult>;
229
+ verifySecure(options: VerifyOptions): Promise<VerificationResult>;
139
230
  getStatus(agentId: string): Promise<StatusResult>;
140
231
  getStatusBatch(agentIds: string[]): Promise<BatchStatusResult>;
232
+ checkRevocation(attestationHash: string): Promise<{ revoked: boolean; checkedAt: string }>;
233
+ renew(agentId: string, options?: object): Promise<VerificationResult>;
234
+
235
+ // STARK proofs (privacy-preserving)
236
+ generateProof(agentId: string, options?: ProofOptions): Promise<STARKProof>;
237
+ generateConsistencyProof(agentId: string, options?: ConsistencyProofOptions): Promise<ConsistencyProof>;
238
+ generateStreakProof(agentId: string, options?: StreakProofOptions): Promise<StreakProof>;
239
+ generateStabilityProof(agentId: string, options?: StabilityProofOptions): Promise<StabilityProof>;
240
+
241
+ // Execution traces
242
+ submitTrace(agentId: string, data: TraceData): Promise<TraceResult>;
243
+ getTraces(agentId: string, options?: { limit?: number }): Promise<{ traces: TraceResult[]; count: number }>;
244
+ getBehavioralScore(agentId: string): Promise<BehavioralScore>;
245
+ anchorTrace(traceId: string): Promise<AnchorResult>;
246
+
247
+ // Staking pools
141
248
  applyToPool(options: PoolApplyOptions): Promise<PoolApplyResult>;
142
249
  getPools(topic?: string): Promise<any>;
143
250
  getLeaderboard(): Promise<any>;
251
+
252
+ // Helpers
144
253
  isHealthy(): Promise<boolean>;
254
+ isVerified(agentId: string): Promise<boolean>;
255
+ checkCapability(agentId: string, capability: string, minScore?: number): Promise<boolean>;
256
+ getProofCost(proofType?: string): Promise<CostEstimate>;
257
+ generateNonce(): string;
145
258
  }
package/src/index.js CHANGED
@@ -252,6 +252,545 @@ class MoltLaunch {
252
252
  return false;
253
253
  }
254
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
+ // ==========================================
491
+
492
+ /**
493
+ * Collect environment fingerprint for hardware-anchored identity
494
+ * @returns {object} Raw fingerprint data
495
+ * @private
496
+ */
497
+ _collectFingerprint() {
498
+ const crypto = require('crypto');
499
+ const os = require('os');
500
+
501
+ const hardware = {
502
+ platform: os.platform(),
503
+ arch: os.arch(),
504
+ cpus: os.cpus().length,
505
+ cpuModel: os.cpus()[0]?.model || 'unknown',
506
+ totalMemory: os.totalmem(),
507
+ hostname: os.hostname(),
508
+ };
509
+
510
+ const runtime = {
511
+ nodeVersion: process.version,
512
+ pid: process.pid,
513
+ execPath: process.execPath,
514
+ cwd: process.cwd(),
515
+ env: {
516
+ USER: process.env.USER || process.env.USERNAME || 'unknown',
517
+ HOME: process.env.HOME || process.env.USERPROFILE || 'unknown',
518
+ SHELL: process.env.SHELL || 'unknown',
519
+ }
520
+ };
521
+
522
+ // Try to get network interfaces for fingerprinting
523
+ const nets = os.networkInterfaces();
524
+ const networkFingerprint = Object.keys(nets).sort().map(name => {
525
+ const iface = nets[name].find(n => !n.internal && n.family === 'IPv4');
526
+ return iface ? `${name}:${iface.mac}` : null;
527
+ }).filter(Boolean).join('|');
528
+
529
+ return { hardware, runtime, networkFingerprint };
530
+ }
531
+
532
+ /**
533
+ * Generate a hardware-anchored identity hash
534
+ * Combines hardware, runtime, code, and network fingerprints into a deterministic identity
535
+ *
536
+ * @param {object} options - Identity options
537
+ * @param {boolean} [options.includeHardware=true] - Include hardware fingerprint (CPU, memory)
538
+ * @param {boolean} [options.includeRuntime=true] - Include runtime fingerprint (Node version, OS)
539
+ * @param {boolean} [options.includeCode=false] - Include code hash (hash of main module)
540
+ * @param {string} [options.codeEntry] - Path to agent's main module for code hashing
541
+ * @param {string} [options.agentId] - Agent ID to bind identity to
542
+ * @param {boolean} [options.anchor=false] - Anchor identity on Solana
543
+ * @returns {Promise<IdentityResult>}
544
+ */
545
+ async generateIdentity(options = {}) {
546
+ const crypto = require('crypto');
547
+ const {
548
+ includeHardware = true,
549
+ includeRuntime = true,
550
+ includeCode = false,
551
+ codeEntry,
552
+ agentId,
553
+ anchor = false
554
+ } = options;
555
+
556
+ const fingerprint = this._collectFingerprint();
557
+ const components = [];
558
+
559
+ if (includeHardware) {
560
+ const hwHash = crypto.createHash('sha256')
561
+ .update(JSON.stringify(fingerprint.hardware))
562
+ .digest('hex');
563
+ components.push(`hw:${hwHash}`);
564
+ }
565
+
566
+ if (includeRuntime) {
567
+ const rtHash = crypto.createHash('sha256')
568
+ .update(JSON.stringify(fingerprint.runtime))
569
+ .digest('hex');
570
+ components.push(`rt:${rtHash}`);
571
+ }
572
+
573
+ if (includeCode && codeEntry) {
574
+ try {
575
+ const fs = require('fs');
576
+ const codeContent = fs.readFileSync(codeEntry, 'utf-8');
577
+ const codeHash = crypto.createHash('sha256')
578
+ .update(codeContent)
579
+ .digest('hex');
580
+ components.push(`code:${codeHash}`);
581
+ } catch (e) {
582
+ components.push(`code:unavailable`);
583
+ }
584
+ }
585
+
586
+ if (fingerprint.networkFingerprint) {
587
+ const netHash = crypto.createHash('sha256')
588
+ .update(fingerprint.networkFingerprint)
589
+ .digest('hex');
590
+ components.push(`net:${netHash}`);
591
+ }
592
+
593
+ // Generate deterministic identity hash
594
+ const identityHash = crypto.createHash('sha256')
595
+ .update(components.join('|'))
596
+ .digest('hex');
597
+
598
+ const identity = {
599
+ hash: identityHash,
600
+ components: components.length,
601
+ includesHardware: includeHardware,
602
+ includesRuntime: includeRuntime,
603
+ includesCode: includeCode && !!codeEntry,
604
+ includesNetwork: !!fingerprint.networkFingerprint,
605
+ generatedAt: new Date().toISOString(),
606
+ expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days
607
+ agentId: agentId || null
608
+ };
609
+
610
+ // Register with MoltLaunch API
611
+ if (agentId) {
612
+ try {
613
+ const res = await fetch(`${this.baseUrl}/api/identity/register`, {
614
+ method: 'POST',
615
+ headers: {
616
+ 'Content-Type': 'application/json',
617
+ ...(this.apiKey && { 'Authorization': `Bearer ${this.apiKey}` })
618
+ },
619
+ body: JSON.stringify({
620
+ agentId,
621
+ identityHash,
622
+ components: components.length,
623
+ includesHardware: includeHardware,
624
+ includesCode: includeCode
625
+ })
626
+ });
627
+
628
+ if (res.ok) {
629
+ const registration = await res.json();
630
+ identity.registered = true;
631
+ identity.registrationId = registration.registrationId;
632
+ } else {
633
+ identity.registered = false;
634
+ identity.registrationError = `API ${res.status}`;
635
+ }
636
+ } catch (e) {
637
+ identity.registered = false;
638
+ identity.registrationError = e.message;
639
+ }
640
+ }
641
+
642
+ // Anchor on Solana if requested
643
+ if (anchor && agentId) {
644
+ try {
645
+ const res = await fetch(`${this.baseUrl}/api/anchor/verification`, {
646
+ method: 'POST',
647
+ headers: { 'Content-Type': 'application/json' },
648
+ body: JSON.stringify({
649
+ agentId,
650
+ attestationHash: identityHash
651
+ })
652
+ });
653
+
654
+ if (res.ok) {
655
+ const anchorResult = await res.json();
656
+ identity.anchored = true;
657
+ identity.anchorSignature = anchorResult.signature;
658
+ identity.anchorExplorer = anchorResult.explorer;
659
+ } else {
660
+ identity.anchored = false;
661
+ }
662
+ } catch (e) {
663
+ identity.anchored = false;
664
+ identity.anchorError = e.message;
665
+ }
666
+ }
667
+
668
+ return identity;
669
+ }
670
+
671
+ /**
672
+ * Verify an agent's identity against their registered fingerprint
673
+ * @param {string} agentId - Agent ID to verify
674
+ * @returns {Promise<IdentityVerification>}
675
+ */
676
+ async verifyIdentity(agentId) {
677
+ // Generate current fingerprint
678
+ const currentIdentity = await this.generateIdentity({ agentId });
679
+
680
+ // Check against registered identity
681
+ try {
682
+ const res = await fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId)}`);
683
+ if (!res.ok) {
684
+ return {
685
+ valid: false,
686
+ agentId,
687
+ reason: 'No registered identity found',
688
+ currentHash: currentIdentity.hash
689
+ };
690
+ }
691
+
692
+ const registered = await res.json();
693
+ const matches = registered.identityHash === currentIdentity.hash;
694
+
695
+ return {
696
+ valid: matches,
697
+ agentId,
698
+ currentHash: currentIdentity.hash,
699
+ registeredHash: registered.identityHash,
700
+ match: matches,
701
+ registeredAt: registered.registeredAt,
702
+ reason: matches ? 'Identity confirmed' : 'Identity mismatch — different hardware or code'
703
+ };
704
+ } catch (e) {
705
+ return {
706
+ valid: false,
707
+ agentId,
708
+ reason: e.message,
709
+ currentHash: currentIdentity.hash
710
+ };
711
+ }
712
+ }
713
+
714
+ /**
715
+ * Check if two agents have the same hardware fingerprint (Sybil detection)
716
+ * @param {string} agentId1 - First agent
717
+ * @param {string} agentId2 - Second agent
718
+ * @returns {Promise<SybilCheck>}
719
+ */
720
+ async checkSybil(agentId1, agentId2) {
721
+ try {
722
+ const [id1, id2] = await Promise.all([
723
+ fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId1)}`).then(r => r.json()),
724
+ fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(agentId2)}`).then(r => r.json())
725
+ ]);
726
+
727
+ const sameIdentity = id1.identityHash === id2.identityHash;
728
+
729
+ return {
730
+ agentId1,
731
+ agentId2,
732
+ sameIdentity,
733
+ sybilRisk: sameIdentity ? 'HIGH' : 'LOW',
734
+ reason: sameIdentity
735
+ ? 'Same hardware fingerprint — likely same operator'
736
+ : 'Different hardware fingerprints — likely different operators',
737
+ recommendation: sameIdentity
738
+ ? 'Do not seat at same table'
739
+ : 'Safe to interact'
740
+ };
741
+ } catch (e) {
742
+ return {
743
+ agentId1,
744
+ agentId2,
745
+ sameIdentity: null,
746
+ sybilRisk: 'UNKNOWN',
747
+ reason: `Could not compare: ${e.message}`
748
+ };
749
+ }
750
+ }
751
+
752
+ /**
753
+ * Check a list of agents for Sybil clusters (table seating check)
754
+ * @param {string[]} agentIds - List of agent IDs to check
755
+ * @returns {Promise<SybilTableCheck>}
756
+ */
757
+ async checkTableSybils(agentIds) {
758
+ // Fetch all identities
759
+ const identities = {};
760
+ for (const id of agentIds) {
761
+ try {
762
+ const res = await fetch(`${this.baseUrl}/api/identity/${encodeURIComponent(id)}`);
763
+ if (res.ok) {
764
+ const data = await res.json();
765
+ identities[id] = data.identityHash;
766
+ }
767
+ } catch {
768
+ // Skip agents without identity
769
+ }
770
+ }
771
+
772
+ // Find clusters (same identity hash)
773
+ const hashToAgents = {};
774
+ for (const [agentId, hash] of Object.entries(identities)) {
775
+ if (!hashToAgents[hash]) hashToAgents[hash] = [];
776
+ hashToAgents[hash].push(agentId);
777
+ }
778
+
779
+ const clusters = Object.values(hashToAgents).filter(group => group.length > 1);
780
+ const flagged = clusters.flat();
781
+
782
+ return {
783
+ totalAgents: agentIds.length,
784
+ identifiedAgents: Object.keys(identities).length,
785
+ unidentifiedAgents: agentIds.filter(id => !identities[id]),
786
+ sybilClusters: clusters,
787
+ flaggedAgents: flagged,
788
+ safe: clusters.length === 0,
789
+ recommendation: clusters.length === 0
790
+ ? 'No Sybil clusters detected — safe to proceed'
791
+ : `${clusters.length} Sybil cluster(s) detected — ${flagged.length} agents share hardware`
792
+ };
793
+ }
255
794
  }
256
795
 
257
796
  // Scoring helpers