/** * Main Credential Manager Implementation * * Central orchestrator for multi-platform credential management. * Provides unified interface for configuration loading, account management, * and signing operations across different platforms. */ import { ICredentialManager, LoadResult, Account, SignResult, Platform, CredentialErrorCode, CredentialManagerError, PerformanceMetrics, } from '@/types/credential' import { ConfigLoader } from './ConfigLoader' import { CredentialValidator } from './CredentialValidator' import { PlatformDetector } from './PlatformDetector' import { PacificaSigner } from './signers/PacificaSigner' /** * Main credential manager class */ export class CredentialManager implements ICredentialManager { private configLoader = new ConfigLoader() private validator = new CredentialValidator() private platformDetector = new PlatformDetector() private accounts = new Map() private signers = new Map() private isWatching = false private currentConfigPath?: string private metrics: PerformanceMetrics constructor() { // Initialize performance metrics this.metrics = { configLoadTime: 0, configWatchLatency: 0, signOperationsTotal: 0, signOperationsSuccess: 0, signOperationsFailed: 0, averageSignTime: 0, maxSignTime: 0, minSignTime: Infinity, verifyOperationsTotal: 0, verifyOperationsSuccess: 0, verifyOperationsFailed: 0, averageVerifyTime: 0, memoryUsage: 0, accountCount: 0, errorsByCode: Object.fromEntries(Object.values(CredentialErrorCode).map(code => [code, 0])) as Record< CredentialErrorCode, number >, lastResetAt: new Date(), uptime: 0, } // Initialize signers this.initializeSigners() } /** * Load configuration file */ async loadConfig(filePath: string): Promise { const startTime = Date.now() try { // Load configuration using ConfigLoader const result = await this.configLoader.loadConfig(filePath) // Update metrics const loadTime = Date.now() - startTime this.metrics.configLoadTime = loadTime if (result.success) { // Clear existing accounts this.accounts.clear() // Add loaded accounts to internal storage for (const account of result.accounts) { this.accounts.set(account.id, account) } this.metrics.accountCount = this.accounts.size this.currentConfigPath = filePath // Validate all accounts have supported platforms for (const account of result.accounts) { if (!this.signers.has(account.platform)) { this.recordError(CredentialErrorCode.PLATFORM_NOT_SUPPORTED) console.warn(`Platform ${account.platform} not supported for account ${account.id}`) } } } else { this.recordError(CredentialErrorCode.CONFIG_VALIDATION_FAILED) } return result } catch (error) { const loadTime = Date.now() - startTime this.metrics.configLoadTime = loadTime this.recordError(CredentialErrorCode.CONFIG_NOT_FOUND) throw new CredentialManagerError( CredentialErrorCode.CONFIG_NOT_FOUND, `Failed to load configuration: ${error instanceof Error ? error.message : 'Unknown error'}`, error, ) } } /** * Watch configuration file for changes */ watchConfig(filePath: string, callback: (accounts: Account[]) => void): void { try { if (this.isWatching && this.currentConfigPath !== filePath) { // Stop watching previous file this.stopWatching() } // Set up file watching this.configLoader.watchConfig(filePath, accounts => { const startTime = Date.now() // Update internal account storage this.accounts.clear() for (const account of accounts) { this.accounts.set(account.id, account) } this.metrics.accountCount = this.accounts.size this.metrics.configWatchLatency = Date.now() - startTime // Call user callback callback(accounts) }) this.isWatching = true this.currentConfigPath = filePath } catch (error) { this.recordError(CredentialErrorCode.FILE_WATCH_FAILED) throw new CredentialManagerError( CredentialErrorCode.FILE_WATCH_FAILED, `Failed to watch configuration file: ${error instanceof Error ? error.message : 'Unknown error'}`, error, ) } } /** * Stop watching configuration file */ stopWatching(): void { try { this.configLoader.stopWatching() this.isWatching = false this.currentConfigPath = undefined } catch (error) { console.warn('Error stopping configuration watching:', error) } } /** * Get account by ID */ getAccount(accountId: string): Account | null { if (!accountId || typeof accountId !== 'string') { return null } return this.accounts.get(accountId) || null } /** * List all accounts */ listAccounts(): Account[] { return Array.from(this.accounts.values()) } /** * Sign message with account */ async sign(accountId: string, message: Uint8Array): Promise { const startTime = Date.now() this.metrics.signOperationsTotal++ try { // Validate inputs if (!accountId || typeof accountId !== 'string') { throw new CredentialManagerError(CredentialErrorCode.ACCOUNT_NOT_FOUND, 'Invalid account ID') } if (!message || message.length === 0) { throw new CredentialManagerError(CredentialErrorCode.INVALID_MESSAGE, 'Message cannot be empty') } // Get account const account = this.getAccount(accountId) if (!account) { throw new CredentialManagerError(CredentialErrorCode.ACCOUNT_NOT_FOUND, `Account not found: ${accountId}`) } // Check if account is enabled if (!account.enabled) { throw new CredentialManagerError(CredentialErrorCode.ACCOUNT_DISABLED, `Account is disabled: ${accountId}`) } // Get appropriate signer const signer = this.signers.get(account.platform) if (!signer) { throw new CredentialManagerError( CredentialErrorCode.PLATFORM_NOT_SUPPORTED, `Platform not supported: ${account.platform}`, ) } // Perform signing const signature = await signer.sign(message, account.credentials) const duration = Date.now() - startTime this.updateSigningMetrics(true, duration) // Update account last used time account.lastUsed = new Date() return { success: true, signature, algorithm: this.getAlgorithmForPlatform(account.platform), timestamp: new Date(), duration, } } catch (error) { const duration = Date.now() - startTime this.updateSigningMetrics(false, duration) if (error instanceof CredentialManagerError) { this.recordError(error.code) return { success: false, algorithm: this.getAlgorithmForPlatform(Platform.PACIFICA), // Default timestamp: new Date(), duration, error: error.message, } } this.recordError(CredentialErrorCode.SIGNING_FAILED) return { success: false, algorithm: this.getAlgorithmForPlatform(Platform.PACIFICA), // Default timestamp: new Date(), duration, error: `Signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`, } } } /** * Verify signature */ async verify(accountId: string, message: Uint8Array, signature: string): Promise { const startTime = Date.now() this.metrics.verifyOperationsTotal++ try { // Validate inputs if (!accountId || !message || !signature) { this.metrics.verifyOperationsFailed++ return false } // Get account const account = this.getAccount(accountId) if (!account) { this.metrics.verifyOperationsFailed++ return false } // Get appropriate signer const signer = this.signers.get(account.platform) if (!signer) { this.metrics.verifyOperationsFailed++ return false } // Get public key for verification let publicKey: string try { publicKey = await this.getPublicKeyForAccount(account) } catch (error) { this.metrics.verifyOperationsFailed++ return false } // Perform verification const isValid = await signer.verify(message, signature, publicKey) const duration = Date.now() - startTime this.updateVerificationMetrics(isValid, duration) return isValid } catch (error) { const duration = Date.now() - startTime this.updateVerificationMetrics(false, duration) return false } } /** * Get performance metrics */ getMetrics(): PerformanceMetrics { // Update uptime this.metrics.uptime = Date.now() - this.metrics.lastResetAt.getTime() // Update memory usage if available if (process.memoryUsage) { this.metrics.memoryUsage = process.memoryUsage().heapUsed } return { ...this.metrics } } /** * Reset performance metrics */ resetMetrics(): void { this.metrics = { configLoadTime: 0, configWatchLatency: 0, signOperationsTotal: 0, signOperationsSuccess: 0, signOperationsFailed: 0, averageSignTime: 0, maxSignTime: 0, minSignTime: Infinity, verifyOperationsTotal: 0, verifyOperationsSuccess: 0, verifyOperationsFailed: 0, averageVerifyTime: 0, memoryUsage: this.metrics.memoryUsage, accountCount: this.accounts.size, errorsByCode: {}, lastResetAt: new Date(), uptime: 0, } } /** * Health check */ async healthCheck(): Promise<{ healthy: boolean components: { configLoader: boolean accounts: boolean signers: Record } performance: { configLoadTime: number averageSignTime: number memoryUsage: number } timestamp: Date }> { const health = { healthy: true, components: { configLoader: true, accounts: this.accounts.size > 0, signers: { [Platform.PACIFICA]: this.signers.has(Platform.PACIFICA), [Platform.ASTER]: this.signers.has(Platform.ASTER), [Platform.BINANCE]: this.signers.has(Platform.BINANCE), }, }, performance: { configLoadTime: this.metrics.configLoadTime, averageSignTime: this.metrics.averageSignTime, memoryUsage: this.metrics.memoryUsage, }, timestamp: new Date(), } // Check overall health health.healthy = health.components.configLoader && Object.values(health.components.signers).some(Boolean) && health.performance.configLoadTime < 100 && health.performance.averageSignTime < 50 return health } /** * Initialize platform signers */ private initializeSigners(): void { // Initialize Pacifica signer this.signers.set(Platform.PACIFICA, new PacificaSigner()) // TODO: Initialize other signers when implemented // this.signers.set(Platform.BINANCE, new BinanceSigner()); // this.signers.set(Platform.ASTER, new AsterSigner()); } /** * Get algorithm for platform */ private getAlgorithmForPlatform(platform: Platform): string { switch (platform) { case Platform.PACIFICA: return 'ed25519' case Platform.ASTER: return 'eip191' case Platform.BINANCE: return 'hmac-sha256' default: return 'unknown' } } /** * Get public key for account */ private async getPublicKeyForAccount(account: Account): Promise { switch (account.platform) { case Platform.PACIFICA: if (account.credentials.type === 'ed25519' && 'privateKey' in account.credentials) { // Derive public key from private key const { PacificaKeyUtils } = await import('./signers/PacificaSigner') return PacificaKeyUtils.derivePublicKey(account.credentials.privateKey) } break case Platform.ASTER: // TODO: Implement Aster public key derivation break case Platform.BINANCE: // HMAC doesn't use public keys for verification return '' } throw new CredentialManagerError( CredentialErrorCode.ACCOUNT_INVALID_CREDENTIALS, `Cannot derive public key for platform ${account.platform}`, ) } /** * Update signing metrics */ private updateSigningMetrics(success: boolean, duration: number): void { if (success) { this.metrics.signOperationsSuccess++ } else { this.metrics.signOperationsFailed++ } // Update timing metrics this.metrics.maxSignTime = Math.max(this.metrics.maxSignTime, duration) this.metrics.minSignTime = Math.min(this.metrics.minSignTime, duration) // Calculate rolling average const totalSuccessfulSigns = this.metrics.signOperationsSuccess if (totalSuccessfulSigns > 0) { this.metrics.averageSignTime = (this.metrics.averageSignTime * (totalSuccessfulSigns - 1) + duration) / totalSuccessfulSigns } } /** * Update verification metrics */ private updateVerificationMetrics(success: boolean, duration: number): void { if (success) { this.metrics.verifyOperationsSuccess++ } else { this.metrics.verifyOperationsFailed++ } // Calculate rolling average for verification time const totalVerifications = this.metrics.verifyOperationsTotal if (totalVerifications > 0) { this.metrics.averageVerifyTime = (this.metrics.averageVerifyTime * (totalVerifications - 1) + duration) / totalVerifications } } /** * Record error in metrics */ private recordError(errorCode: CredentialErrorCode): void { this.metrics.errorsByCode[errorCode] = (this.metrics.errorsByCode[errorCode] || 0) + 1 } /** * Validate account credentials */ validateAccount(accountId: string): { isValid: boolean errors: string[] warnings: string[] } { const account = this.getAccount(accountId) if (!account) { return { isValid: false, errors: [`Account not found: ${accountId}`], warnings: [], } } return this.validator.validateAccountConfig(account) } /** * Get supported platforms */ getSupportedPlatforms(): Platform[] { return Array.from(this.signers.keys()) } /** * Check if platform is supported */ isPlatformSupported(platform: Platform): boolean { return this.signers.has(platform) } }