Services Layer
The services layer offers integrations with external systems: blockchain RPC nodes, the OrderBook API, and the database.
API Service
Location: src/services/api.ts
The API service manages a REST API and Prometheus metrics endpoint.
Class: ApiService
Singleton service managing an Express server.
class ApiService {
protected port: number;
protected app: Express;
protected server: Server | null;
protected chainContexts: ChainContext[];
static getInstance(port?: number): ApiService
async start(): Promise<Server>
async stop(): Promise<void>
setChainContexts(chainContexts: ChainContext[]): void
getChainContexts(): ChainContext[]
}
Endpoints
Root endpoint
Metrics
- GET
/metrics - Prometheus metrics
Health check
- GET
/health - Returns health status of all chains
- Response includes overall health and per-chain status
Configuration
- GET
/config - Returns current watch-tower configuration
- Shows chain IDs, contracts, deployment blocks, and filter policies
API endpoints
- GET
/api/version - Version information
- GET
/api/dump/:chainId - Dumps registry for specified chain ID
Usage
const api = ApiService.getInstance(8080);
await api.start();
// Set chain contexts after initialization
api.setChainContexts(chainContexts);
// Later, stop the server
await api.stop();
Configuration
Disable the API server using the --disable-api flag:
yarn cli run --disable-api
Configure the port:
yarn cli run --api-port 3000
Chain Service
Location: src/services/chain.ts
The chain service manages blockchain interactions and order monitoring for each network.
Class: ChainContext
Represents a single blockchain network being monitored.
class ChainContext {
readonly deploymentBlock: number;
readonly pageSize: number;
readonly dryRun: boolean;
readonly watchdogTimeout: number;
readonly addresses?: string[];
readonly processEveryNumBlocks: number;
provider: providers.Provider;
chainId: SupportedChainId;
registry: Registry;
orderBookApi: OrderBookApi;
filterPolicy: FilterPolicy | undefined;
contract: ComposableCoW;
multicall: Multicall3;
static async init(options: ContextOptions, storage: DBService): Promise<ChainContext>
async warmUp(oneShot?: boolean): Promise<void>
get health(): ChainHealth
static get health(): ChainWatcherHealth
}
Initialization
const context = await ChainContext.init({
rpc: 'https://eth-mainnet.g.alchemy.com/v2/...',
deploymentBlock: 16842080,
pageSize: 5000,
dryRun: false,
// ... other options
}, storage);
Warm-up Phase
The warm-up phase synchronizes historical blocks:
- Loads last processed block from database
- Fetches events from
deploymentBlock to current block
- Processes events in pages (configurable size)
- Updates registry with new conditional orders
- Transitions to
IN_SYNC state
Block Watching
After warm-up, subscribes to new blocks:
await context.warmUp();
// Now watching for new blocks
For each new block:
- Fetches
ConditionalOrderCreated events
- Processes new conditional orders
- Polls existing orders for discrete order creation
- Updates database atomically
Health Monitoring
// Single chain health
const chainHealth = context.health;
// { sync: 'IN_SYNC', chainId: 1, lastProcessedBlock: {...}, isHealthy: true }
// All chains health
const overallHealth = ChainContext.health;
// { overallHealth: true, chains: { 1: {...}, 5: {...} } }
Sync States
- SYNCING: Catching up with historical blocks
- IN_SYNC: Caught up, watching new blocks
- UNKNOWN: Watchdog timeout exceeded (may indicate RPC issues)
Provider Support
Supports both HTTP and WebSocket providers:
// WebSocket
rpc: 'wss://eth-mainnet.ws.alchemy.com/v2/...'
// HTTP
rpc: 'https://eth-mainnet.g.alchemy.com/v2/...'
Storage Service
Location: src/services/storage.ts
The storage service provides database operations using LevelDB.
Class: DBService
Singleton service for database operations.
class DBService {
protected db: Level<string, string>;
static getInstance(path?: string): DBService
async open(): Promise<void>
async close(): Promise<void>
getDB(): Level<string, string>
}
Configuration
Database options:
const options: DatabaseOptions<string, string> = {
valueEncoding: 'json', // Store as JSON
createIfMissing: true, // Create if doesn't exist
errorIfExists: false, // Allow opening existing DB
};
Usage
// Get singleton instance
const storage = DBService.getInstance('./database');
// Open connection
await storage.open();
// Access underlying LevelDB
const db = storage.getDB();
await db.get('key');
await db.put('key', 'value');
// Batch operations
await db.batch()
.put('key1', 'value1')
.put('key2', 'value2')
.write();
// Close connection
await storage.close();
Default Location
Default database path: ./database
Configure the path:
yarn cli run --database-path /path/to/db
ACID Guarantees
LevelDB provides:
- Atomicity: Batch operations are atomic
- Consistency: Schema validation on read/write
- Isolation: Single-threaded Node.js
- Durability: Writes are persisted to disk
Service Initialization Order
- DBService: Initialize database connection
- ApiService: Start REST API server (if enabled)
- ChainContext: Initialize per network
- Create provider
- Load registry from database
- Start chain monitoring
Service Shutdown
Graceful shutdown process:
await Promise.allSettled([
ApiService.getInstance().stop(),
DBService.getInstance().close(),
]);
Last modified on March 4, 2026