2026-03-02 12:14:09 -08:00
|
|
|
import express, { Express, Request, Response, NextFunction } from 'express';
|
|
|
|
|
import cors from 'cors';
|
|
|
|
|
import compression from 'compression';
|
|
|
|
|
import { apiRateLimiter, strictRateLimiter } from './middleware/rate-limit';
|
|
|
|
|
import tokenRoutes from './routes/tokens';
|
|
|
|
|
import reportRoutes from './routes/report';
|
|
|
|
|
import adminRoutes from './routes/admin';
|
|
|
|
|
import configRoutes from './routes/config';
|
|
|
|
|
import bridgeRoutes from './routes/bridge';
|
|
|
|
|
import quoteRoutes from './routes/quote';
|
2026-03-27 12:02:36 -07:00
|
|
|
import routeTreeRoutes from './routes/routes';
|
2026-03-02 12:14:09 -08:00
|
|
|
import tokenMappingRoutes from './routes/token-mapping';
|
2026-03-04 02:00:09 -08:00
|
|
|
import heatmapRoutes from './routes/heatmap';
|
|
|
|
|
import arbitrageRoutes from './routes/arbitrage';
|
2026-03-27 12:02:36 -07:00
|
|
|
import aggregatorRouteMatrixRoutes from './routes/aggregator-routes';
|
|
|
|
|
import partnerPayloadRoutes from './routes/partner-payloads';
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
import plannerV2Routes from './routes/planner-v2';
|
2026-03-02 12:14:09 -08:00
|
|
|
import { MultiChainIndexer } from '../indexer/chain-indexer';
|
|
|
|
|
import { getDatabasePool } from '../database/client';
|
|
|
|
|
import winston from 'winston';
|
|
|
|
|
|
|
|
|
|
// Setup logger
|
|
|
|
|
const logger = winston.createLogger({
|
|
|
|
|
level: process.env.LOG_LEVEL || 'info',
|
|
|
|
|
format: winston.format.combine(
|
|
|
|
|
winston.format.timestamp(),
|
|
|
|
|
winston.format.errors({ stack: true }),
|
|
|
|
|
winston.format.json()
|
|
|
|
|
),
|
|
|
|
|
transports: [
|
|
|
|
|
new winston.transports.Console({
|
|
|
|
|
format: winston.format.combine(
|
|
|
|
|
winston.format.colorize(),
|
|
|
|
|
winston.format.simple()
|
|
|
|
|
),
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export class ApiServer {
|
|
|
|
|
private app: Express;
|
|
|
|
|
private port: number;
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
private indexerEnabled: boolean;
|
|
|
|
|
private indexer: MultiChainIndexer | null;
|
|
|
|
|
|
|
|
|
|
private resolveTrustProxySetting(): boolean | number | string {
|
|
|
|
|
const raw = (process.env.EXPRESS_TRUST_PROXY ?? process.env.TRUST_PROXY ?? '1').trim();
|
|
|
|
|
const normalized = raw.toLowerCase();
|
|
|
|
|
|
|
|
|
|
if (normalized === 'true') return true;
|
|
|
|
|
if (normalized === 'false') return false;
|
|
|
|
|
if (/^\d+$/.test(raw)) return parseInt(raw, 10);
|
|
|
|
|
return raw;
|
|
|
|
|
}
|
2026-03-02 12:14:09 -08:00
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.app = express();
|
|
|
|
|
this.port = parseInt(process.env.PORT || '3000', 10);
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
this.indexerEnabled = this.resolveFeatureFlag('ENABLE_INDEXER', true);
|
|
|
|
|
this.indexer = this.indexerEnabled ? new MultiChainIndexer() : null;
|
2026-03-02 12:14:09 -08:00
|
|
|
|
|
|
|
|
this.setupMiddleware();
|
|
|
|
|
this.setupRoutes();
|
|
|
|
|
this.setupErrorHandling();
|
|
|
|
|
}
|
|
|
|
|
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
private resolveFeatureFlag(name: string, fallback: boolean): boolean {
|
|
|
|
|
const raw = (process.env[name] || '').trim().toLowerCase();
|
|
|
|
|
if (!raw) return fallback;
|
|
|
|
|
if (['1', 'true', 'yes', 'on'].includes(raw)) return true;
|
|
|
|
|
if (['0', 'false', 'no', 'off'].includes(raw)) return false;
|
|
|
|
|
return fallback;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 12:14:09 -08:00
|
|
|
private setupMiddleware(): void {
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
const trustProxy = this.resolveTrustProxySetting();
|
|
|
|
|
this.app.set('trust proxy', trustProxy);
|
|
|
|
|
|
2026-03-02 12:14:09 -08:00
|
|
|
// CORS
|
|
|
|
|
this.app.use(cors());
|
|
|
|
|
|
|
|
|
|
// Compression
|
|
|
|
|
this.app.use(compression());
|
|
|
|
|
|
|
|
|
|
// Body parsing
|
|
|
|
|
this.app.use(express.json());
|
|
|
|
|
this.app.use(express.urlencoded({ extended: true }));
|
|
|
|
|
|
|
|
|
|
// Rate limiting
|
|
|
|
|
this.app.use('/api/v1', apiRateLimiter);
|
|
|
|
|
|
|
|
|
|
// Request logging
|
|
|
|
|
this.app.use((req: Request, res: Response, next: NextFunction) => {
|
|
|
|
|
logger.info(`${req.method} ${req.path}`, {
|
|
|
|
|
ip: req.ip,
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
forwardedFor: req.get('x-forwarded-for'),
|
|
|
|
|
trustProxy,
|
2026-03-02 12:14:09 -08:00
|
|
|
userAgent: req.get('user-agent'),
|
|
|
|
|
});
|
|
|
|
|
next();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private setupRoutes(): void {
|
|
|
|
|
// Health check
|
|
|
|
|
this.app.get('/health', async (req: Request, res: Response) => {
|
|
|
|
|
try {
|
|
|
|
|
// Check database connection
|
|
|
|
|
const pool = getDatabasePool();
|
|
|
|
|
await pool.query('SELECT 1');
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
status: 'healthy',
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
services: {
|
|
|
|
|
database: 'connected',
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
indexer: this.indexerEnabled ? 'running' : 'disabled',
|
2026-03-02 12:14:09 -08:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(503).json({
|
|
|
|
|
status: 'unhealthy',
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// API routes
|
|
|
|
|
this.app.use('/api/v1', tokenRoutes);
|
|
|
|
|
this.app.use('/api/v1', configRoutes);
|
|
|
|
|
this.app.use('/api/v1/report', reportRoutes);
|
|
|
|
|
this.app.use('/api/v1/bridge', bridgeRoutes);
|
2026-03-27 12:02:36 -07:00
|
|
|
this.app.use('/api/v1', routeTreeRoutes);
|
2026-03-02 12:14:09 -08:00
|
|
|
this.app.use('/api/v1/token-mapping', tokenMappingRoutes);
|
|
|
|
|
this.app.use('/api/v1', quoteRoutes);
|
2026-03-04 02:00:09 -08:00
|
|
|
this.app.use('/api/v1', heatmapRoutes);
|
|
|
|
|
this.app.use('/api/v1', arbitrageRoutes);
|
2026-03-27 12:02:36 -07:00
|
|
|
this.app.use('/api/v1', aggregatorRouteMatrixRoutes);
|
|
|
|
|
this.app.use('/api/v1', partnerPayloadRoutes);
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
this.app.use('/api/v2', plannerV2Routes);
|
2026-03-02 12:14:09 -08:00
|
|
|
|
|
|
|
|
// Admin routes (stricter rate limit)
|
|
|
|
|
this.app.use('/api/v1/admin', strictRateLimiter, adminRoutes);
|
|
|
|
|
|
|
|
|
|
// Root
|
|
|
|
|
this.app.get('/', (req: Request, res: Response) => {
|
|
|
|
|
res.json({
|
|
|
|
|
name: 'Token Aggregation Service',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
endpoints: {
|
|
|
|
|
health: '/health',
|
|
|
|
|
api: '/api/v1',
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
apiV2: '/api/v2',
|
2026-03-02 12:14:09 -08:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private setupErrorHandling(): void {
|
|
|
|
|
// 404 handler
|
|
|
|
|
this.app.use((req: Request, res: Response) => {
|
|
|
|
|
res.status(404).json({ error: 'Not found' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Error handler
|
2026-03-04 02:00:09 -08:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Express error handler requires 4-arg signature
|
|
|
|
|
this.app.use((err: Error, req: Request, res: Response, _next: NextFunction) => {
|
2026-03-02 12:14:09 -08:00
|
|
|
logger.error('Unhandled error:', err);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
error: 'Internal server error',
|
|
|
|
|
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async start(): Promise<void> {
|
|
|
|
|
try {
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
if (this.indexer) {
|
|
|
|
|
await this.indexer.initialize();
|
|
|
|
|
await this.indexer.startAll();
|
|
|
|
|
} else {
|
|
|
|
|
logger.info('Token aggregation indexer disabled by ENABLE_INDEXER flag');
|
|
|
|
|
}
|
2026-03-02 12:14:09 -08:00
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
|
this.app.listen(this.port, () => {
|
|
|
|
|
logger.info(`Token Aggregation Service listening on port ${this.port}`);
|
|
|
|
|
logger.info(`Health check: http://localhost:${this.port}/health`);
|
|
|
|
|
logger.info(`API: http://localhost:${this.port}/api/v1`);
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('Failed to start server:', error);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async stop(): Promise<void> {
|
feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.
Run forge build && forge test before deploy (large solc graph).
Made-with: Cursor
2026-04-07 23:40:52 -07:00
|
|
|
this.indexer?.stopAll();
|
2026-03-02 12:14:09 -08:00
|
|
|
logger.info('Server stopped');
|
|
|
|
|
}
|
|
|
|
|
}
|