This document describes the architectural design, structure, and key design decisions for the Sentinel project.
Sentinel is a real-time mempool monitoring system for detecting suspicious smart contract activities. The architecture is designed for:
- Scalability: Handle multiple blockchain networks simultaneously
- Modularity: Independent, testable components
- Extensibility: Easy to add new detection rules and integrations
- Reliability: Fault-tolerant with proper error handling
- Observability: Built-in tracing and monitoring
┌─────────────────────────────────────────────────────────────┐
│ Sentinel Backend (NestJS) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ API Layer (Controllers) │ │
│ │ - Alert Management - Configuration │ │
│ │ - Webhook Management - Health Checks │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Business Logic Layer (Modules) │ │
│ │ ┌────────────┐ ┌────────────┐ ┌─────────────┐ │ │
│ │ │ Monitoring │ │ Alerts │ │ Signatures │ │ │
│ │ │ Module │ │ Module │ │ Module │ │ │
│ │ └────────────┘ └────────────┘ └─────────────┘ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌─────────────┐ │ │
│ │ │ Webhooks │ │ Audit Log │ │ Health │ │ │
│ │ │ Module │ │ Module │ │ Module │ │ │
│ │ └────────────┘ └────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Integrations Layer │ │
│ │ ┌────────────┐ ┌────────────┐ ┌─────────────┐ │ │
│ │ │ Blockchain │ │ Discord │ │ Telegram │ │ │
│ │ │Integration │ │Integration │ │ Integration │ │ │
│ │ └────────────┘ └────────────┘ └─────────────┘ │ │
│ │ ┌────────────┐ │ │
│ │ │ PagerDuty │ │ │
│ │ │Integration │ │ │
│ │ └────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Cross-Cutting Concerns (Common) │ │
│ │ - Authentication/Authorization │ │
│ │ - Validation & Transformation (Pipes) │ │
│ │ - Error Handling (Filters) │ │
│ │ - Logging & Tracing │ │
│ │ - Shared Constants & Interfaces │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
├─────────────────────────────────────────────────────────────┤
│ Data Layer │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ PostgreSQL │ │ Redis │ │
│ │ (Persistent) │ │ (Cache) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
sentinel/
├── apps/
│ └── backend/
│ └── src/
│ ├── main.ts # Application entry point
│ ├── app.module.ts # Root module
│ ├── app.controller.ts # Root controller
│ ├── modules/ # Business logic modules
│ │ ├── alerts/
│ │ ├── monitoring/
│ │ ├── signatures/
│ │ ├── webhooks/
│ │ ├── audit-log/
│ │ └── health/
│ ├── common/ # Shared utilities
│ │ ├── decorators/
│ │ ├── filters/
│ │ ├── guards/
│ │ ├── pipes/
│ │ ├── interfaces/
│ │ ├── utils/
│ │ ├── constants/
│ │ └── middleware/
│ ├── integrations/ # External service integrations
│ │ ├── discord/
│ │ ├── telegram/
│ │ ├── blockchain/
│ │ └── pagerduty/
│ └── config/ # Configuration management
├── database/ # Shared database module
├── libs/
│ └── notify/ # Notification library
├── prisma/ # Database schema
├── observability/ # OpenTelemetry setup
└── [Docker files, CI config, etc.]
Modules are the core organizational units containing business logic:
- Cohesive: Each module handles one domain (alerts, monitoring, etc.)
- Independent: Can be developed and tested separately
- Composable: Import and use services from other modules
- Testable: All logic is injectable and mockable
@Module({
imports: [TypeOrmModule.forFeature([AlertEntity])],
controllers: [AlertController],
providers: [AlertService],
exports: [AlertService], // Available to other modules
})
export class AlertsModule {}Shared code used across all modules:
- Decorators: Reusable annotations (@Audit, @CacheResult, etc.)
- Guards: Authentication, authorization, rate limiting
- Pipes: Validation, transformation, sanitization
- Filters: Exception handling and error formatting
- Utils: Helper functions
- Interfaces: Shared TypeScript types
- Constants: Enum values and fixed constants
- Middleware: Request/response processing
Adapters for external services:
- Blockchain: RPC providers for Stellar, Ethereum, Polygon
- Notifications: Discord, Telegram, PagerDuty
- Isolated: Each integration is self-contained
- Testable: Easy to mock in tests
- Resilient: Retry logic, circuit breakers
┌─────────────────────────────────────────────────────────────┐
│ 1. Blockchain Integration │
│ - Connect to RPC providers │
│ - Monitor mempool for new transactions │
└─────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Monitoring Module │
│ - Fetch pending transactions │
│ - Parse transaction calldata │
└─────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Signature Detection │
│ - Extract function signatures │
│ - Compare against malicious signature database │
│ - Calculate matching confidence │
└─────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Alert Rules Evaluation │
│ - Check if matches any alert rules │
│ - Evaluate severity and priority │
│ - Determine notification channels │
└─────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. Alert Module │
│ - Create alert record │
│ - Store in PostgreSQL │
│ - Trigger webhook handlers │
└─────────────────┬───────────────────────────────────────────┘
│
┌─────────┴──────────┬──────────────┬──────────────┐
▼ ▼ ▼ ▼
Discord Bot Telegram Bot PagerDuty Webhooks
Notification Notification Incident Circuit
Creation Breaker
│ │ │ │
└─────────────────────┴──────────────┴──────────────┘
│
▼
┌───────────────────────┐
│ Audit Log Module │
│ - Log all activities │
│ - Track outcomes │
│ - Maintain audit trail│
└───────────────────────┘
Each module/service has one reason to change.
// ✓ Good: Clear responsibility
class SignatureDetectorService {
detectMaliciousSignature(calldata: string): boolean { }
}
// ✗ Bad: Multiple responsibilities
class SignatureService {
detectMalicious() { }
sendNotification() { }
logActivity() { }
}Use NestJS DI for loose coupling.
@Injectable()
export class AlertService {
constructor(
private readonly signatureService: SignatureDetectorService,
private readonly discordService: DiscordService,
) {}
}Small, specific interfaces instead of large monolithic ones.
interface Alertable {
send(payload: AlertPayload): Promise<void>;
}
interface Healthcheckable {
isHealthy(): Promise<boolean>;
}
// Services implement only what they need
class DiscordService implements Alertable, Healthcheckable { }Open for extension, closed for modification.
// Add new integrations without modifying existing code
abstract class NotificationService {
abstract send(alert: Alert): Promise<void>;
}
class SlackService extends NotificationService {
// New implementation
}Depend on abstractions, not concrete implementations.
// ✓ Good: Depends on interface
constructor(private alerter: Alertable) { }
// ✗ Bad: Depends on concrete class
constructor(private discord: DiscordService) { }Key entities in PostgreSQL:
// Alerts
interface Alert {
id: string;
ruleId: string;
severity: AlertSeverity;
transactionHash: string;
detectedAt: Date;
status: AlertStatus;
createdAt: Date;
}
// Alert Rules
interface AlertRule {
id: string;
name: string;
signature: string;
enabled: boolean;
notificationChannels: string[];
createdAt: Date;
}
// Audit Logs
interface AuditLog {
id: string;
action: string;
entityType: string;
entityId: string;
changes: Record<string, unknown>;
actor: string;
createdAt: Date;
}
// Webhooks
interface Webhook {
id: string;
url: string;
events: string[];
headers: Record<string, string>;
active: boolean;
createdAt: Date;
}// Cache malicious signatures for quick lookups
CACHE_KEYS = {
SIGNATURES: 'signatures:all',
ALERT_RULES: 'alert_rules:active',
USER_PREFERENCES: 'user:preferences:{userId}',
}
// Cache TTL values
CACHE_TTL = {
SIGNATURES: 3600, // 1 hour
ALERT_RULES: 1800, // 30 minutes
USER_PREFERENCES: 7200, // 2 hours
}abstract class SentinelException extends Error {
abstract getStatus(): number;
abstract getErrorCode(): string;
}
export class SignatureNotFoundException extends SentinelException {
getStatus() { return 404; }
getErrorCode() { return 'SIGNATURE_NOT_FOUND'; }
}
export class InvalidAlertRuleException extends SentinelException {
getStatus() { return 400; }
getErrorCode() { return 'INVALID_ALERT_RULE'; }
}@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: Error, host: ArgumentsHost) {
// Format and send error response
// Log error with tracing
// Track in observability system
}
}@Injectable()
export class ApiKeyGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Validate API key from request headers
}
}@Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Check user roles
}
}
@UseGuards(RolesGuard)
@Roles('admin', 'moderator')
@Post('/admin-action')
adminAction() { }All services include automatic tracing:
@Span('alert.create')
async createAlert(dto: CreateAlertDto) {
// Automatically traced and monitored
}Logger.log() // Info level
Logger.warn() // Warning level
Logger.error() // Error level
Logger.debug() // Debug level (dev only)
Logger.verbose() // Detailed traces- Test services in isolation
- Mock dependencies
- Focus on business logic
- Test module interactions
- Use test database
- Validate data flows
- Test complete workflows
- Full stack testing
- Docker-based test environment
src/
├── modules/
│ └── alerts/
│ ├── alerts.service.ts
│ ├── alerts.service.spec.ts
│ ├── alerts.controller.ts
│ └── alerts.controller.spec.ts
- Cache malicious signatures (static)
- Cache alert rules (TTL-based)
- User preferences (session-based)
- Indices on frequently queried columns
- Pagination for list endpoints
- Connection pooling
// Use queues for heavy operations
this.auditQueue.add({ action: 'ALERT_CREATED', data: alert });- Stateless services
- Load balancer (nginx/Traefik)
- Shared PostgreSQL & Redis
- Increase Node.js memory
- Optimize database queries
- Implement caching effectively
- Isolate data by tenant
- Separate webhook namespaces
- Per-tenant rate limiting
- API Keys: Validate all requests
- Input Validation: Use pipes and decorators
- Rate Limiting: Prevent abuse
- CORS: Configure appropriately
- Secrets: Use environment variables
- HTTPS: Enforce in production
- Logging: Never log sensitive data
- Dependencies: Regular security audits
- Multi-stage builds for optimization
- Non-root user execution
- Health checks configured
- Stateless service design
- ConfigMaps for configuration
- Secrets for sensitive data
- Resource limits defined
- Linting and type checking
- Automated testing
- Build verification
- Artifact storage
- Machine Learning: Pattern detection using ML
- Multi-Tenancy: Support multiple organizations
- Advanced Analytics: Historical analysis and reports
- Custom Rules: User-defined detection rules
- API Marketplace: Third-party integrations
- Mobile Alerts: Native mobile app
- Kubernetes Support: K8s deployment guides
When adding new features:
- Create feature branch
- Follow module structure
- Add unit tests
- Update documentation
- Submit pull request
- Ensure CI passes
- Request code review