Slowtime MCP Server

by bmorphism
Verified
import { HttpCachingChain, HttpChainClient, ChainInfo } from 'drand-client'; import { timelockEncrypt, timelockDecrypt, roundForTime } from 'tlock-js'; import { timeVault } from './timevault.js'; // Default to League of Entropy's Mainnet const CHAIN_INFO: ChainInfo = { hash: '8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce', public_key: '868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784bc9402c6bc2f6c003621e75f6631788b4b46f8b91f51', period: 3, genesis_time: 1677685200, url: 'https://api.drand.sh' }; export interface TimeLockData { id: string; encryptedData: string; roundNumber: number; decryptedData?: string; } export class TimeLockManager { private client: HttpChainClient; private timeLocks: Map<string, TimeLockData>; constructor() { this.client = new HttpCachingChain(CHAIN_INFO.url, CHAIN_INFO); this.timeLocks = new Map(); } async encryptForInterval(data: string, intervalDurationMs: number): Promise<TimeLockData> { // Calculate the round number for the future time const futureTime = Date.now() + intervalDurationMs; const roundNumber = roundForTime(CHAIN_INFO, futureTime); // Encrypt the data const encryptedData = await timelockEncrypt( new TextEncoder().encode(data), roundNumber, this.client ); const id = crypto.randomUUID(); const timeLockData: TimeLockData = { id, encryptedData: Buffer.from(encryptedData).toString('base64'), roundNumber }; // Store in memory and persistent storage this.timeLocks.set(timeLockData.id, timeLockData); await timeVault.storeVault({ id, encryptedData: timeLockData.encryptedData, roundNumber, createdAt: Date.now(), intervalId: crypto.randomUUID(), // TODO: Pass actual interval ID metadata: JSON.stringify({ durationMs: intervalDurationMs }) }); return timeLockData; } async attemptDecryption(id: string): Promise<string | null> { const timeLock = this.timeLocks.get(id); if (!timeLock) { throw new Error('TimeLock not found'); } if (timeLock.decryptedData) { return timeLock.decryptedData; } try { const encryptedData = Buffer.from(timeLock.encryptedData, 'base64'); const decrypted = await timelockDecrypt(encryptedData, this.client); const decryptedText = new TextDecoder().decode(decrypted); // Update both in-memory and persistent storage timeLock.decryptedData = decryptedText; this.timeLocks.set(id, timeLock); await timeVault.markDecrypted(id); return decryptedText; } catch (error: unknown) { if (error instanceof Error && error.message.includes('round not reached')) { return null; // Not yet decryptable } throw error; } } getTimeLock(id: string): TimeLockData | undefined { return this.timeLocks.get(id); } listTimeLocks(): TimeLockData[] { return Array.from(this.timeLocks.values()); } // Clean up old timelocks that have been decrypted async cleanupDecrypted(maxAgeMs: number = 24 * 60 * 60 * 1000): Promise<void> { // Clean up memory for (const [id, timeLock] of this.timeLocks.entries()) { if (timeLock.decryptedData) { this.timeLocks.delete(id); } } // Clean up persistent storage await timeVault.cleanup(maxAgeMs); } } // Create a singleton instance export const timeLockManager = new TimeLockManager();