Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 1 addition & 192 deletions backend/src/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,190 +29,6 @@ const parseHistorySource = (value: unknown): 'all' | 'offchain' | 'simulated' |
return 'all'
}

// ================================
// HEALTH CHECK ROUTES
// ================================

// Health check with enhanced status
router.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
mode: deploymentMode,
features: {
contract_deployed: true,
real_price_feeds: true,
automatic_monitoring: true,
circuit_breakers: true,
demo_portfolios: featureFlags.demoMode,
risk_management: true,
rebalance_history: true,
auto_rebalancer: autoRebalancer ? autoRebalancer.getStatus().isRunning : false,
flags: publicFeatureFlags
}
})
})

// ================================
// PORTFOLIO MANAGEMENT ROUTES
// ================================

// Create portfolio with enhanced validation
router.post('/portfolio', writeRateLimiter, async (req, res) => {
try {
const { userAddress, allocations, threshold } = req.body

if (!userAddress || !allocations || threshold === undefined) {
return res.status(400).json({ error: 'Missing required fields: userAddress, allocations, threshold' })
}

// Enhanced validation
const total = Object.values(allocations as Record<string, number>).reduce((sum, val) => sum + val, 0)
if (Math.abs(total - 100) > 0.01) {
return res.status(400).json({ error: 'Allocations must sum to 100%' })
}

// Validate threshold range
if (threshold < 1 || threshold > 50) {
return res.status(400).json({ error: 'Threshold must be between 1% and 50%' })
}

// Validate asset allocations
for (const [asset, percentage] of Object.entries(allocations as Record<string, number>)) {
if (percentage < 0 || percentage > 100) {
return res.status(400).json({ error: `Invalid percentage for ${asset}: must be between 0-100%` })
}
}

const portfolioId = await stellarService.createPortfolio(userAddress, allocations, threshold)

const reflector = new ReflectorService()
const prices = await reflector.getCurrentPrices()
await analyticsService.captureSnapshot(portfolioId, prices)

await rebalanceHistoryService.recordRebalanceEvent({
portfolioId,
trigger: 'Portfolio Created',
trades: 0,
gasUsed: '0 XLM',
status: 'completed',
isAutomatic: false
})

logger.info('Portfolio created successfully', {
portfolioId,
userAddress,
allocations,
threshold,
mode: deploymentMode
})

res.json({
portfolioId,
status: 'created',
mode: deploymentMode,
message: featureFlags.demoMode
? 'Portfolio created with simulated $10,000 balance'
: 'Portfolio created with real on-chain balances'
})
} catch (error) {
logger.error('Failed to create portfolio', { error: getErrorObject(error) })
res.status(500).json({
error: getErrorMessage(error)
})
}
})

// Get portfolio with real-time data
router.get('/portfolio/:id', async (req, res) => {
try {
const portfolioId = req.params.id

if (!portfolioId) {
return res.status(400).json({ error: 'Portfolio ID required' })
}

const portfolio = await stellarService.getPortfolio(portfolioId)
const prices = await reflectorService.getCurrentPrices()

await analyticsService.captureSnapshot(portfolioId, prices)

let riskMetrics = null
try {
const allocationsRecord = getPortfolioAllocationsAsRecord(portfolio)
riskMetrics = riskManagementService.analyzePortfolioRisk(allocationsRecord, prices)
} catch (riskError) {
console.warn('Risk analysis failed:', riskError)
}

res.json({
portfolio,
prices,
riskMetrics,
mode: deploymentMode,
lastUpdated: new Date().toISOString()
})
} catch (error) {
logger.error('Failed to fetch portfolio', { error: getErrorObject(error), portfolioId: req.params.id })
res.status(500).json({
error: getErrorMessage(error)
})
}
})

// Get user portfolios
router.get('/user/:address/portfolios', async (req, res) => {
try {
const userAddress = req.params.address
const portfolios = await portfolioStorage.getUserPortfolios(userAddress)

res.json(portfolios)
} catch (error) {
logger.error('Failed to fetch user portfolios', { error: getErrorObject(error), userAddress: req.params.address })
res.status(500).json({ error: 'Failed to fetch portfolios' })
}
})

// ================================
// REBALANCING ROUTES
// ================================

// Enhanced rebalance with comprehensive safety checks
router.post('/portfolio/:id/rebalance', writeRateLimiter, async (req, res) => {
try {
const portfolioId = req.params.id

if (!portfolioId) {
return res.status(400).json({ error: 'Portfolio ID required' })
}

// Get current prices for safety checks
const prices = await reflectorService.getCurrentPrices()

// Check circuit breakers before proceeding
const marketCheck = await CircuitBreakers.checkMarketConditions(prices)
if (!marketCheck.safe) {
return res.status(400).json({
error: `Rebalance blocked by safety systems: ${marketCheck.reason}`,
reason: 'circuit_breaker',
canRetry: true
})
}

// Enhanced risk management check
const portfolio = await stellarService.getPortfolio(portfolioId)
const riskCheck = riskManagementService.shouldAllowRebalance(portfolio, prices)

if (!riskCheck.allowed) {
return res.status(400).json({
error: `Rebalance blocked by risk management: ${riskCheck.reason}`,
reason: 'risk_management',
reasonCode: riskCheck.reasonCode,
alerts: riskCheck.alerts,
riskMetrics: riskCheck.riskMetrics,
canRetry: true
})
}

router.get('/rebalance/history', async (req, res) => {
try {
Expand Down Expand Up @@ -341,14 +157,7 @@ router.get('/risk/metrics/:portfolioId', async (req, res) => {
concentrationRisk: 0,
liquidityRisk: 0,
correlationRisk: 0,
overallRiskLevel: 'low' as const,
ewmaVolatility: 0,
var95: 0,
cvar95: 0,
maxDrawdown: 0,
drawdownBand: 'normal' as const,
correlations: {},
sampleSize: 0

}
})
}
Expand Down
36 changes: 1 addition & 35 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@

import { validateStartupConfigOrThrow, buildStartupSummary, type StartupConfig } from './config/startupConfig.js'
import { getFeatureFlags, getPublicFeatureFlags } from './config/featureFlags.js'
import { isRedisAvailable, logQueueStartup } from './queue/connection.js'
import { closeAllQueues } from './queue/queues.js'
import { startQueueScheduler } from './queue/scheduler.js'
import { startPortfolioCheckWorker, stopPortfolioCheckWorker } from './queue/workers/portfolioCheckWorker.js'
import { startRebalanceWorker, stopRebalanceWorker } from './queue/workers/rebalanceWorker.js'
import { startAnalyticsSnapshotWorker, stopAnalyticsSnapshotWorker } from './queue/workers/analyticsSnapshotWorker.js'

if (shouldStartAutoRebalancer) {
try {
console.log('[AUTO-REBALANCER] Starting automatic rebalancing service...')
await autoRebalancer.start()
Expand All @@ -27,17 +17,7 @@ import { startAnalyticsSnapshotWorker, stopAnalyticsSnapshotWorker } from './que
} catch (error) {
console.error('[AUTO-REBALANCER] ❌ Failed to start automatic rebalancing service:', error)
}
} else {
console.log('[AUTO-REBALANCER] Automatic rebalancing disabled in development mode')
console.log('[AUTO-REBALANCER] Set ENABLE_AUTO_REBALANCER=true to enable in development')
}


try {
await contractEventIndexerService.start()
} catch (error) {
console.error('[CHAIN-INDEXER] Failed to start:', error)
}

console.log('Available endpoints:')
console.log(` Health: http://localhost:${port}/health`)
Expand All @@ -51,10 +31,7 @@ import { startAnalyticsSnapshotWorker, stopAnalyticsSnapshotWorker } from './que
const gracefulShutdown = async (signal: string) => {
console.log(`\n[SHUTDOWN] ${signal} received, shutting down gracefully...`)

// Stop auto-rebalancer
try {
autoRebalancer.stop()
console.log('[SHUTDOWN] Auto-rebalancer stopped')

} catch (error) {
console.error('[SHUTDOWN] Error stopping auto-rebalancer:', error)
}
Expand All @@ -79,18 +56,7 @@ const gracefulShutdown = async (signal: string) => {
console.error('[SHUTDOWN] Error closing queues:', error)
}

// Close database connection
try {
await contractEventIndexerService.stop()
console.log('[SHUTDOWN] Contract event indexer stopped')
} catch (error) {
console.error('[SHUTDOWN] Error stopping contract event indexer:', error)
}

try {
databaseService.close()
console.log('[SHUTDOWN] Database connection closed')
} catch (error) {
console.error('[SHUTDOWN] Error closing database:', error)
}

Expand Down