@mraicodedev/bsc-token-detector
Version:
🚀 Real-time BSC token detector with advanced filtering, health monitoring, and comprehensive event system. Detect new tokens on Binance Smart Chain as they are created!
1,196 lines (1,034 loc) • 36.7 kB
JavaScript
import { EventEmitter } from 'events';
import { ErrorFactory } from './utils/Errors.js';
import { ConfigurationManager } from './utils/ConfigurationManager.js';
import { APIDocumentation } from './utils/APIDocumentation.js';
import { Logger } from './utils/Logger.js';
import { ErrorRecoveryManager } from './utils/ErrorRecovery.js';
import { HealthMonitor, HealthStatus } from './utils/HealthMonitor.js';
import { RPCConnectionPool } from './core/RPCConnectionPool.js';
import { BlockScanner } from './core/BlockScanner.js';
import { MempoolMonitor } from './core/MempoolMonitor.js';
import { EventMonitor } from './core/EventMonitor.js';
import { TokenAnalyzer } from './core/TokenAnalyzer.js';
import { FilterEngine } from './core/FilterEngine.js';
import { ResultHandler } from './core/ResultHandler.js';
/**
* Main BSC Token Detector class
* Orchestrates all components to detect new tokens on Binance Smart Chain
*/
export class BSCTokenDetector extends EventEmitter {
constructor(config) {
super();
// Initialize configuration manager
this.configManager = new ConfigurationManager();
// Initialize and validate configuration
this.config = this.configManager.initialize(config || {});
// Initialize logger
this.logger = new Logger().child('BSCTokenDetector');
// Initialize error recovery system
this.errorRecovery = new ErrorRecoveryManager({
enableCircuitBreaker: true,
enableRetry: true,
circuitBreakerConfig: {
failureThreshold: this.config.errorHandling?.circuitBreakerThreshold || 5,
resetTimeout: this.config.errorHandling?.circuitBreakerResetTime || 60000
},
retryConfig: {
maxAttempts: this.config.errorHandling?.maxRetryAttempts || 3,
initialDelay: this.config.errorHandling?.retryDelay || 1000,
maxDelay: this.config.errorHandling?.maxRetryDelay || 30000
}
});
// Initialize health monitoring system
this.healthMonitor = new HealthMonitor({
checkInterval: this.config.monitoring?.healthCheckInterval || 30000,
enableAutoCheck: this.config.monitoring?.enableHealthCheck !== false,
alertThresholds: {
consecutiveFailures: 3,
failureRate: 50,
responseTime: 5000
}
});
// Initialize state
this.isRunning = false;
this.startTime = 0;
this.components = {};
// Error handling state
this.errorStats = {
totalErrors: 0,
recoveredErrors: 0,
criticalErrors: 0,
lastError: null,
errorsByType: new Map()
};
// Initialize all components
this.initializeComponents();
// Setup component event handlers
this.setupEventHandlers();
// Setup error handling and health monitoring
this.setupErrorHandling();
this.setupHealthMonitoring();
this.logger.info('BSC Token Detector initialized successfully with enhanced error handling');
}
/**
* Start the token detection process with error recovery
*/
async start() {
if (this.isRunning) {
throw new Error('Detector is already running');
}
this.logger.info('Starting BSC Token Detector with enhanced error handling...');
try {
// Start with error recovery
await this.errorRecovery.executeWithRecovery(async () => {
this.isRunning = true;
this.startTime = Date.now();
// Start health monitoring first
this.healthMonitor.start();
// Start components in order with circuit breaker protection
await this.errorRecovery.executeWithRecovery(
() => this.components.blockScanner.start(),
{
operationName: 'startBlockScanner',
circuitBreakerName: 'blockScanner',
context: { component: 'blockScanner' }
}
);
if (this.config.monitoring.enableMempool) {
await this.errorRecovery.executeWithRecovery(
() => this.components.mempoolMonitor.start(),
{
operationName: 'startMempoolMonitor',
circuitBreakerName: 'mempoolMonitor',
context: { component: 'mempoolMonitor' }
}
);
}
// Perform initial health check
await this.healthCheck();
return true;
}, {
operationName: 'startDetector',
context: { operation: 'start' }
});
this.logger.info('BSC Token Detector started successfully');
this.emit('statusChanged', this.getSystemStatus());
} catch (error) {
this.logger.error('Failed to start BSC Token Detector:', error);
this.isRunning = false;
// Stop health monitoring if it was started
this.healthMonitor.stop();
// Handle the startup error
await this.handleError(error, 'startup');
throw error;
}
}
/**
* Stop the token detection process gracefully
*/
async stop() {
if (!this.isRunning) {
return;
}
this.logger.info('Stopping BSC Token Detector gracefully...');
this.isRunning = false;
try {
// Stop components gracefully with timeout protection
const stopOperations = [];
stopOperations.push(
this.errorRecovery.executeWithRecovery(
() => this.components.blockScanner.stop(),
{
operationName: 'stopBlockScanner',
enableCircuitBreaker: false, // Don't use circuit breaker for shutdown
context: { component: 'blockScanner', operation: 'stop' }
}
)
);
stopOperations.push(
this.errorRecovery.executeWithRecovery(
() => this.components.mempoolMonitor.stop(),
{
operationName: 'stopMempoolMonitor',
enableCircuitBreaker: false,
context: { component: 'mempoolMonitor', operation: 'stop' }
}
)
);
// Wait for all components to stop (with timeout)
await Promise.race([
Promise.allSettled(stopOperations),
this.createTimeoutPromise(10000, 'Component shutdown timeout')
]);
// Stop health monitoring
this.healthMonitor.stop();
// Reset error recovery state
this.errorRecovery.resetAllCircuitBreakers();
this.logger.info('BSC Token Detector stopped successfully');
this.emit('statusChanged', this.getSystemStatus());
} catch (error) {
this.logger.error('Error stopping BSC Token Detector:', error);
await this.handleError(error, 'shutdown');
throw error;
}
}
/**
* Update configuration at runtime
*/
configure(newConfig, options = {}) {
try {
const result = this.configManager.updateConfiguration(newConfig, options);
const oldConfig = result.oldConfig;
this.config = result.newConfig;
// Update component configurations
if (newConfig.filters && this.components?.filterEngine) {
this.components.filterEngine.updateFilters(newConfig.filters);
}
if (newConfig.performance && this.components?.resultHandler) {
this.components.resultHandler.config = {
...this.components.resultHandler.config,
...newConfig.performance
};
}
// Update RPC pool configuration if needed
if (newConfig.rpcProviders && this.components?.rpcPool) {
this.components.rpcPool.updateProviders(newConfig.rpcProviders);
}
// Update monitoring configuration if needed
if (newConfig.monitoring) {
if (this.components?.blockScanner && typeof this.components.blockScanner.updateConfig === 'function') {
this.components.blockScanner.updateConfig(newConfig.monitoring);
}
if (this.components?.mempoolMonitor && typeof this.components.mempoolMonitor.updateConfig === 'function') {
this.components.mempoolMonitor.updateConfig(newConfig.monitoring);
}
}
this.logger.info('Configuration updated successfully');
this.emit('configurationChanged', { oldConfig, newConfig: this.config });
return result;
} catch (error) {
this.logger.error('Failed to update configuration:', error);
throw error;
}
}
/**
* Get current detector status
*/
getStatus() {
const resultStats = this.components.resultHandler.getStats();
return {
isRunning: this.isRunning,
currentBlock: this.components.blockScanner.getCurrentBlock(),
confirmedBlock: this.components.blockScanner.getConfirmedBlock(),
tokensDetected: resultStats.totalDetected,
pendingTokens: resultStats.totalPending,
uptime: this.isRunning ? Date.now() - this.startTime : 0,
rpcHealth: this.components.rpcPool.getStatus(),
components: {
blockScanner: this.components.blockScanner.getStatus(),
mempoolMonitor: this.components.mempoolMonitor.getStatus(),
eventMonitor: { isHealthy: true, lastCheck: Date.now(), errorCount: 0 },
tokenAnalyzer: this.components.tokenAnalyzer.getCacheStats(),
filterEngine: this.components.filterEngine.getFilterStats(),
resultHandler: resultStats
},
config: {
enableMempool: this.config.monitoring.enableMempool,
pollInterval: this.config.monitoring.pollInterval,
blockConfirmations: this.config.performance.blockConfirmations,
activeFilters: this.components.filterEngine.hasActiveFilters()
}
};
}
/**
* Get list of detected tokens
*/
getDetectedTokens() {
return this.components.resultHandler.getResults();
}
/**
* Get pending tokens
*/
getPendingTokens() {
return this.components.resultHandler.getPendingResults();
}
/**
* Get all results (detected + pending)
*/
getAllResults() {
return this.components.resultHandler.getAllResults();
}
/**
* Search tokens by criteria
*/
searchTokens(criteria) {
return this.components.resultHandler.searchResults(criteria);
}
/**
* Clear all results
*/
clearResults() {
this.components.resultHandler.clearResults();
this.logger.info('All results cleared');
}
/**
* Register callback for token detection events
*/
onTokenDetected(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('tokenDetected', callback);
return this; // Enable method chaining
}
/**
* Register callback for pending token detection events
*/
onPendingToken(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('pendingToken', callback);
return this;
}
/**
* Register callback for error events
*/
onError(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('error', callback);
return this;
}
/**
* Register callback for enhanced error events
*/
onEnhancedError(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('enhancedError', callback);
return this;
}
/**
* Register callback for status change events
*/
onStatusChanged(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('statusChanged', callback);
return this;
}
/**
* Register callback for configuration change events
*/
onConfigurationChanged(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('configurationChanged', callback);
return this;
}
/**
* Register callback for health status change events
*/
onHealthStatusChanged(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('healthStatusChanged', callback);
return this;
}
/**
* Register callback for health alert events
*/
onHealthAlert(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('healthAlert', callback);
return this;
}
/**
* Register callback for circuit breaker activation events
*/
onCircuitBreakerActivated(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('circuitBreakerActivated', callback);
return this;
}
/**
* Register callback for provider recovery events
*/
onProviderRecovered(callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
this.on('providerRecovered', callback);
return this;
}
/**
* Register multiple callbacks at once
*/
registerCallbacks(callbacks) {
if (!callbacks || typeof callbacks !== 'object') {
throw new Error('Callbacks must be an object');
}
const validEvents = [
'tokenDetected', 'pendingToken', 'error', 'enhancedError',
'statusChanged', 'configurationChanged', 'healthStatusChanged',
'healthAlert', 'circuitBreakerActivated', 'providerRecovered'
];
for (const [event, callback] of Object.entries(callbacks)) {
if (!validEvents.includes(event)) {
throw new Error(`Invalid event: ${event}. Valid events are: ${validEvents.join(', ')}`);
}
if (typeof callback !== 'function') {
throw new Error(`Callback for event '${event}' must be a function`);
}
this.on(event, callback);
}
return this;
}
/**
* Remove specific callback for an event
*/
removeCallback(event, callback) {
this.removeListener(event, callback);
return this;
}
/**
* Remove all callbacks for a specific event
*/
removeAllCallbacks(event) {
this.removeAllListeners(event);
return this;
}
/**
* Get list of registered events and their listener counts
*/
getRegisteredEvents() {
const events = this.eventNames();
const eventInfo = {};
for (const event of events) {
eventInfo[event] = {
listenerCount: this.listenerCount(event),
maxListeners: this.getMaxListeners()
};
}
return eventInfo;
}
/**
* Initialize all components
*/
initializeComponents() {
try {
// Initialize RPC connection pool
this.components.rpcPool = new RPCConnectionPool(
this.config.rpcProviders,
this.config.performance
);
// Initialize block scanner
this.components.blockScanner = new BlockScanner(
this.components.rpcPool,
this.config.monitoring
);
// Initialize mempool monitor
this.components.mempoolMonitor = new MempoolMonitor(
this.components.rpcPool,
{
pollInterval: this.config.monitoring.mempoolPollInterval,
enabled: this.config.monitoring.enableMempool
}
);
// Initialize event monitor
this.components.eventMonitor = new EventMonitor(this.components.rpcPool);
// Initialize token analyzer
this.components.tokenAnalyzer = new TokenAnalyzer(this.components.rpcPool);
// Initialize filter engine
this.components.filterEngine = new FilterEngine(this.config.filters);
// Initialize result handler
this.components.resultHandler = new ResultHandler({
maxResults: this.config.performance.cacheSize,
enableDeduplication: true
});
this.logger.debug('All components initialized successfully');
} catch (error) {
this.logger.error('Failed to initialize components:', error);
throw error;
}
}
/**
* Setup event handlers between components with error recovery
*/
setupEventHandlers() {
// Block scanner events with error recovery
this.components.blockScanner.on('newBlock', async (blockData) => {
await this.errorRecovery.executeWithRecovery(async () => {
await this.components.eventMonitor.analyzeBlock(blockData);
}, {
operationName: 'analyzeBlock',
circuitBreakerName: 'eventMonitor',
context: { blockNumber: blockData.number }
}).catch(error => {
this.handleError(error, 'blockAnalysis');
});
});
// Mempool monitor events with error recovery
this.components.mempoolMonitor.on('pendingTransaction', async (txData) => {
await this.errorRecovery.executeWithRecovery(async () => {
// Apply pending filters first
if (this.components.filterEngine.applyPendingFilters(txData)) {
await this.components.eventMonitor.analyzePendingTx(txData);
}
}, {
operationName: 'analyzePendingTransaction',
circuitBreakerName: 'eventMonitor',
context: { txHash: txData.hash }
}).catch(error => {
this.handleError(error, 'pendingTxAnalysis');
});
});
this.components.mempoolMonitor.on('pendingTokenDetected', async (tokenInfo) => {
await this.errorRecovery.executeWithRecovery(async () => {
const processedPending = this.components.resultHandler.processPendingToken(tokenInfo);
if (processedPending) {
this.emit('pendingToken', processedPending);
}
}, {
operationName: 'processPendingToken',
enableCircuitBreaker: false, // Result processing shouldn't use circuit breaker
context: { contractAddress: tokenInfo.contractAddress }
}).catch(error => {
this.handleError(error, 'pendingTokenProcessing');
});
});
// Event monitor events with error recovery
this.components.eventMonitor.on('tokenCreated', async (tokenInfo) => {
await this.errorRecovery.executeWithRecovery(async () => {
// Get detailed token information with error recovery
const detailedToken = await this.errorRecovery.executeWithRecovery(
() => this.components.tokenAnalyzer.getTokenInfo(tokenInfo.contractAddress),
{
operationName: 'getTokenInfo',
circuitBreakerName: 'tokenAnalyzer',
context: { contractAddress: tokenInfo.contractAddress }
}
);
// Merge with creation info
const completeTokenInfo = {
...detailedToken,
...tokenInfo,
creationBlock: tokenInfo.creationBlock,
creationTimestamp: tokenInfo.creationTimestamp,
transactionHash: tokenInfo.transactionHash
};
// Apply filters
if (this.components.filterEngine.applyFilters(completeTokenInfo)) {
const processedToken = this.components.resultHandler.processDetectedToken(completeTokenInfo);
if (processedToken) {
this.emit('tokenDetected', processedToken);
}
}
}, {
operationName: 'processDetectedToken',
enableCircuitBreaker: false,
context: { contractAddress: tokenInfo.contractAddress }
}).catch(error => {
this.handleError(error, 'tokenProcessing');
});
});
this.components.eventMonitor.on('pendingTokenCreation', async (pendingInfo) => {
await this.errorRecovery.executeWithRecovery(async () => {
const analyzedPending = await this.errorRecovery.executeWithRecovery(
() => this.components.tokenAnalyzer.analyzePendingToken(pendingInfo),
{
operationName: 'analyzePendingToken',
circuitBreakerName: 'tokenAnalyzer',
context: { txHash: pendingInfo.transactionHash }
}
);
if (analyzedPending) {
const processedPending = this.components.resultHandler.processPendingToken(analyzedPending);
if (processedPending) {
this.emit('pendingToken', processedPending);
}
}
}, {
operationName: 'processPendingTokenCreation',
enableCircuitBreaker: false,
context: { txHash: pendingInfo.transactionHash }
}).catch(error => {
this.handleError(error, 'pendingTokenCreationProcessing');
});
});
// Component error handling with enhanced error processing
const components = ['blockScanner', 'mempoolMonitor', 'rpcPool'];
components.forEach(componentName => {
if (this.components[componentName]) {
this.components[componentName].on('error', async (error) => {
await this.handleError(error, componentName);
});
// Handle circuit breaker events from RPC pool
if (componentName === 'rpcPool') {
this.components[componentName].on('circuitBreakerActivated', (event) => {
this.logger.warn(`Circuit breaker activated for RPC provider`, event);
this.emit('circuitBreakerActivated', event);
});
this.components[componentName].on('providerRecovered', (event) => {
this.logger.info(`RPC provider recovered`, event);
this.emit('providerRecovered', event);
});
}
}
});
this.logger.debug('Enhanced event handlers setup completed');
}
/**
* Create timeout promise utility
*/
createTimeoutPromise(timeout, message = 'Operation timeout') {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(message));
}, timeout);
});
}
/**
* Sleep utility
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Get error recovery status
*/
getErrorRecoveryStatus() {
return this.errorRecovery.getStats();
}
/**
* Get health monitoring status
*/
getHealthMonitoringStatus() {
return this.healthMonitor.getMetrics();
}
/**
* Force circuit breaker state for testing/debugging
*/
setCircuitBreakerState(name, state) {
this.errorRecovery.setCircuitBreakerState(name, state);
this.logger.info(`Circuit breaker '${name}' forced to state: ${state}`);
}
/**
* Reset all circuit breakers
*/
resetCircuitBreakers() {
this.errorRecovery.resetAllCircuitBreakers();
this.logger.info('All circuit breakers reset');
}
/**
* Enable/disable health check for specific component
*/
setHealthCheckEnabled(component, enabled) {
return this.healthMonitor.setHealthCheckEnabled(component, enabled);
}
/**
* Get recent health alerts
*/
getHealthAlerts(limit = 50) {
return this.healthMonitor.getRecentAlerts(limit);
}
/**
* Clear health alert history
*/
clearHealthAlerts() {
this.healthMonitor.clearAlerts();
}
/**
* Get component by name
*/
getComponent(name) {
return this.components[name];
}
/**
* Get current configuration
*/
getConfig() {
return this.configManager.getCurrentConfiguration();
}
/**
* Get configuration section
*/
getConfigSection(sectionName) {
return this.configManager.getConfigurationSection(sectionName);
}
/**
* Reset configuration to defaults
*/
resetConfig() {
try {
const result = this.configManager.resetToDefaults();
this.config = result;
this.logger.info('Configuration reset to defaults');
this.emit('configurationReset', { newConfig: this.config });
return result;
} catch (error) {
this.logger.error('Failed to reset configuration:', error);
throw error;
}
}
/**
* Export configuration
*/
exportConfig(format = 'json') {
return this.configManager.exportConfiguration(format);
}
/**
* Import configuration
*/
importConfig(configString, options = {}) {
try {
const result = this.configManager.importConfiguration(configString, options);
this.config = result.newConfig;
this.logger.info('Configuration imported successfully');
this.emit('configurationImported', result);
return result;
} catch (error) {
this.logger.error('Failed to import configuration:', error);
throw error;
}
}
/**
* Get configuration history
*/
getConfigHistory(limit = 10) {
return this.configManager.getConfigurationHistory(limit);
}
/**
* Get configuration schema
*/
getConfigSchema() {
return this.configManager.getSchema();
}
/**
* Get default configuration
*/
getDefaultConfig() {
return this.configManager.getDefaults();
}
/**
* Validate configuration without applying it
*/
validateConfig(config) {
return this.configManager.validateOnly(config);
}
/**
* Get configuration statistics
*/
getConfigStats() {
return this.configManager.getConfigurationStats();
}
/**
* Get API documentation
*/
static getAPIDocumentation() {
return APIDocumentation.getFullDocumentation();
}
/**
* Generate API documentation in markdown format
*/
static generateAPIDocumentation() {
return APIDocumentation.generateMarkdown();
}
/**
* Get detailed statistics
*/
getDetailedStats() {
return {
detector: this.getStatus(),
rpcPool: this.components.rpcPool.getStatus(),
blockScanner: this.components.blockScanner.getStatus(),
mempoolMonitor: this.components.mempoolMonitor.getStatus(),
tokenAnalyzer: this.components.tokenAnalyzer.getCacheStats(),
filterEngine: this.components.filterEngine.getDetailedStatus(),
resultHandler: this.components.resultHandler.getStats()
};
}
/**
* Export results in various formats
*/
exportResults(format = 'json') {
return this.components.resultHandler.exportResults(format);
}
/**
* Wait for a specific transaction to be confirmed and analyzed
*/
async waitForTokenConfirmation(txHash, maxWaitTime = 300000) {
return this.components.tokenAnalyzer.waitForConfirmation(txHash, maxWaitTime);
}
/**
* Register callback for token confirmation
*/
onTokenConfirmation(txHash, callback) {
this.components.tokenAnalyzer.onConfirmation(txHash, callback);
}
/**
* Cleanup old data and optimize performance
*/
cleanup() {
try {
this.components.tokenAnalyzer.cleanupPendingTokens();
// Clear old error statistics
if (this.errorStats.errorsByType.size > 100) {
this.errorStats.errorsByType.clear();
this.errorStats.totalErrors = 0;
this.errorStats.recoveredErrors = 0;
this.errorStats.criticalErrors = 0;
}
// Clear old health alerts
this.healthMonitor.clearAlerts();
this.logger.debug('Cleanup completed successfully');
} catch (error) {
this.logger.error('Error during cleanup:', error);
}
}
/**
* Destroy detector and cleanup all resources
*/
async destroy() {
this.logger.info('Destroying BSC Token Detector...');
try {
// Stop if running
if (this.isRunning) {
await this.stop();
}
// Destroy health monitor
this.healthMonitor.destroy();
// Destroy error recovery
this.errorRecovery.destroy();
// Destroy configuration manager
if (this.configManager) {
this.configManager.destroy();
}
// Destroy RPC pool
if (this.components.rpcPool) {
this.components.rpcPool.destroy();
}
// Clear all event listeners
this.removeAllListeners();
this.logger.info('BSC Token Detector destroyed successfully');
} catch (error) {
this.logger.error('Error during destruction:', error);
throw error;
}
}
/**
* Setup comprehensive error handling
*/
setupErrorHandling() {
// Handle unhandled errors
this.on('error', (error) => {
this.handleError(error, 'detector');
});
// Setup error recovery event handlers
this.errorRecovery.on = this.errorRecovery.on || (() => {});
// Setup configuration manager event handlers
if (this.configManager && typeof this.configManager.on === 'function') {
this.configManager.on('configurationUpdated', (event) => {
this.emit('configurationChanged', event);
});
this.configManager.on('configurationReset', (event) => {
this.emit('configurationReset', event);
});
this.configManager.on('configurationImported', (event) => {
this.emit('configurationImported', event);
});
this.configManager.on('configurationUpdateFailed', (event) => {
this.emit('configurationUpdateFailed', event);
});
}
this.logger.debug('Error handling system initialized');
}
/**
* Setup health monitoring for all components
*/
setupHealthMonitoring() {
// Register health checks for each component
this.healthMonitor.registerHealthCheck('rpcPool', async () => {
const status = this.components.rpcPool.getStatus();
return {
status: status.pool.healthyProviders > 0 ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY,
message: `${status.pool.healthyProviders}/${status.pool.totalProviders} providers healthy`,
details: status
};
}, { critical: true, timeout: 5000 });
this.healthMonitor.registerHealthCheck('blockScanner', async () => {
const status = this.components.blockScanner.getStatus();
return {
status: status.isHealthy ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY,
message: status.isHealthy ? 'Block scanner operational' : 'Block scanner unhealthy',
details: status
};
}, { critical: true, timeout: 3000 });
this.healthMonitor.registerHealthCheck('mempoolMonitor', async () => {
const status = this.components.mempoolMonitor.getStatus();
return {
status: status.isHealthy ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY,
message: status.isHealthy ? 'Mempool monitor operational' : 'Mempool monitor unhealthy',
details: status
};
}, { critical: false, timeout: 3000 });
this.healthMonitor.registerHealthCheck('tokenAnalyzer', async () => {
const stats = this.components.tokenAnalyzer.getCacheStats();
const isHealthy = stats.errorRate < 0.1; // Less than 10% error rate
return {
status: isHealthy ? HealthStatus.HEALTHY : HealthStatus.DEGRADED,
message: `Token analyzer error rate: ${(stats.errorRate * 100).toFixed(1)}%`,
details: stats
};
}, { critical: false, timeout: 2000 });
this.healthMonitor.registerHealthCheck('filterEngine', async () => {
const stats = this.components.filterEngine.getFilterStats();
return {
status: HealthStatus.HEALTHY,
message: 'Filter engine operational',
details: stats
};
}, { critical: false, timeout: 1000 });
// Setup health monitor event handlers
this.healthMonitor.on('statusChanged', (event) => {
this.logger.info(`System health status changed: ${event.previousStatus} -> ${event.currentStatus}`, {
message: event.message
});
this.emit('healthStatusChanged', event);
});
this.healthMonitor.on('alert', (alert) => {
this.logger.warn(`Health alert: ${alert.message}`, alert);
this.emit('healthAlert', alert);
});
this.logger.debug('Health monitoring system initialized');
}
/**
* Handle errors with recovery mechanisms
*/
async handleError(error, context = 'unknown') {
// Update error statistics
this.errorStats.totalErrors++;
this.errorStats.lastError = {
error: error.message,
context,
timestamp: new Date().toISOString()
};
// Track error by type
const errorType = error.constructor.name;
const currentCount = this.errorStats.errorsByType.get(errorType) || 0;
this.errorStats.errorsByType.set(errorType, currentCount + 1);
// Wrap unknown errors
const wrappedError = ErrorFactory.wrapUnknownError(error, { context });
// Log error with appropriate level
if (wrappedError.isRecoverable()) {
this.logger.warn(`Recoverable error in ${context}:`, wrappedError.toJSON());
} else {
this.logger.error(`Critical error in ${context}:`, wrappedError.toJSON());
this.errorStats.criticalErrors++;
}
// Attempt recovery if error is recoverable
if (wrappedError.isRecoverable()) {
try {
await this.attemptErrorRecovery(wrappedError, context);
this.errorStats.recoveredErrors++;
} catch (recoveryError) {
this.logger.error(`Error recovery failed for ${context}:`, recoveryError);
}
}
// Emit error event with enhanced information
this.emit('enhancedError', {
originalError: error,
wrappedError,
context,
recoverable: wrappedError.isRecoverable(),
retryable: wrappedError.isRetryable(),
stats: this.getErrorStats()
});
}
/**
* Attempt error recovery based on error type and context
*/
async attemptErrorRecovery(error, context) {
switch (context) {
case 'rpcPool':
// RPC pool errors - trigger health check and failover
await this.components.rpcPool.performHealthCheck();
break;
case 'blockScanner':
// Block scanner errors - restart if critical
if (error.code === 'CRITICAL_ERROR') {
this.logger.info('Attempting to restart block scanner due to critical error');
await this.components.blockScanner.stop();
await this.sleep(5000); // Wait 5 seconds
await this.components.blockScanner.start();
}
break;
case 'tokenAnalyzer':
// Token analyzer errors - clear cache if corrupted
if (error.code === 'PARSE_ERROR') {
this.logger.info('Clearing token analyzer cache due to parse error');
this.components.tokenAnalyzer.clearCache();
}
break;
default:
this.logger.debug(`No specific recovery mechanism for context: ${context}`);
}
}
/**
* Get error statistics
*/
getErrorStats() {
return {
...this.errorStats,
errorsByType: Object.fromEntries(this.errorStats.errorsByType),
recoveryRate: this.errorStats.totalErrors > 0 ?
(this.errorStats.recoveredErrors / this.errorStats.totalErrors) * 100 : 0
};
}
/**
* Health check for all components (enhanced version)
*/
async healthCheck() {
try {
return await this.healthMonitor.performHealthChecks();
} catch (error) {
this.logger.error('Health check failed:', error);
return {
status: HealthStatus.CRITICAL,
message: `Health check failed: ${error.message}`,
lastCheck: new Date().toISOString(),
components: {},
metrics: {}
};
}
}
/**
* Get comprehensive system status including health and error information
*/
getSystemStatus() {
const baseStatus = this.getStatus();
const healthStatus = this.healthMonitor.getSystemHealth();
const errorStats = this.getErrorStats();
const recoveryStats = this.errorRecovery.getStats();
return {
...baseStatus,
health: healthStatus,
errorHandling: {
stats: errorStats,
recovery: recoveryStats,
circuitBreakers: recoveryStats.circuitBreakers
},
monitoring: this.healthMonitor.getMetrics()
};
}
}