// Deal Orchestrator Service // Main service that orchestrates the entire freeze-resistant arbitrage loop import { Decimal } from '@prisma/client/runtime/library'; import { logger } from '@/infrastructure/monitoring/logger'; import { v4 as uuidv4 } from 'uuid'; import { DealExecutionRequest, DealExecutionResult, DealState, DealStep, } from './types'; import { riskControlService } from './risk-control.service'; import { stepExecutionService } from './step-execution.service'; import { redemptionTestService } from './redemption-test.service'; import { metricsService } from './services/monitoring/metrics.service'; import { riskMonitorService } from './services/risk-monitor.service'; import { alertService } from './services/monitoring/alert.service'; export class DealOrchestratorService { async executeDeal( request: DealExecutionRequest ): Promise { const dealId = `DEAL-${uuidv4()}`; const startTime = Date.now(); logger.info('Starting Deal Execution', { dealId, totalEthValue: request.totalEthValue, }); // Record deal start in metrics metricsService.updateActiveDeals('active', 1); const state: DealState = { dealId, step: DealStep.INITIALIZED, buckets: { coreEth: new Decimal(0), workingLiquidity: new Decimal(0), opportunisticUsdtz: new Decimal(0), }, onChainTxHashes: {}, errors: [], createdAt: new Date(), updatedAt: new Date(), }; const riskChecks: DealExecutionResult['riskChecks'] = []; const redemptionTests: DealExecutionResult['redemptionTests'] = []; try { const totalNav = new Decimal(request.totalEthValue); const initialRiskCheck = await riskControlService.validateDealRequest( request, totalNav ); riskChecks.push(initialRiskCheck); if (!initialRiskCheck.passed) { throw new Error( `Initial risk check failed: ${initialRiskCheck.errors.join(', ')}` ); } state.step = DealStep.CAPITAL_SPLIT; const step0Start = Date.now(); const step0Result = await stepExecutionService.executeStep0(request); metricsService.recordStepExecution('step0', (Date.now() - step0Start) / 1000); state.buckets = step0Result.buckets; state.updatedAt = new Date(); // Register for risk monitoring riskMonitorService.registerDeal(state); state.step = DealStep.WORKING_LIQUIDITY_GENERATED; const step1Result = await stepExecutionService.executeStep1( state.buckets, request ); state.collateralAmount = step1Result.collateralSupplied; state.borrowedAmount = step1Result.borrowedUsdt; if (step1Result.borrowTxHash) { state.onChainTxHashes['borrow'] = step1Result.borrowTxHash; } if (step1Result.supplyTxHash) { state.onChainTxHashes['supply'] = step1Result.supplyTxHash; } state.updatedAt = new Date(); const postBorrowRiskCheck = await riskControlService.checkLtvCompliance( step1Result.collateralSupplied, step1Result.borrowedUsdt ); riskChecks.push(postBorrowRiskCheck); state.step = DealStep.ARBITRAGE_EXECUTED; const step2Result = await stepExecutionService.executeStep2( step1Result.borrowedUsdt, request ); state.usdtzAcquired = step2Result.usdtzReceived; if (step2Result.swapTxHash) { state.onChainTxHashes['swap'] = step2Result.swapTxHash; } state.updatedAt = new Date(); const usdtzExposureCheck = await riskControlService.checkUsdtzExposure( step2Result.usdtzReceived, totalNav ); riskChecks.push(usdtzExposureCheck); if (!usdtzExposureCheck.passed) { logger.warn('USDTz exposure exceeds limit, but continuing (non-critical)', { errors: usdtzExposureCheck.errors, }); } state.step = DealStep.MONETIZATION_ATTEMPTED; const testResults = await redemptionTestService.executeProgressiveTests( step2Result.usdtzReceived ); redemptionTests.push(...testResults); const step3Result = await stepExecutionService.executeStep3( step2Result.usdtzReceived, request ); state.usdtzRedeemed = step3Result.usdtzForRedemption; state.usdtzColdStorage = step3Result.usdtzForColdStorage; if (step3Result.redemptionTxHash) { state.onChainTxHashes['redemption'] = step3Result.redemptionTxHash; } state.updatedAt = new Date(); let step4Result; if (step3Result.redemptionSuccessful && step3Result.usdtReceived) { state.step = DealStep.LOOP_CLOSED; step4Result = await stepExecutionService.executeStep4( step1Result.borrowedUsdt, step3Result.usdtReceived, step3Result.usdtzForColdStorage, step1Result.collateralSupplied ); if (step4Result.repayTxHash) { state.onChainTxHashes['repay'] = step4Result.repayTxHash; } if (step4Result.unlockTxHash) { state.onChainTxHashes['unlock'] = step4Result.unlockTxHash; } state.updatedAt = new Date(); } else { state.step = DealStep.FROZEN; logger.warn('Deal degraded to holding state', { reason: 'USDTz redemption failed or frozen', note: 'ETH collateral remains safe, loan is healthy, USDTz is optional upside', }); } let finalProfit: Decimal | undefined; if (step4Result) { finalProfit = step4Result.profitCaptured; } const status: DealExecutionResult['status'] = state.step === DealStep.LOOP_CLOSED ? 'completed' : state.step === DealStep.FROZEN ? 'frozen' : 'partial'; logger.info('Deal Execution Complete', { dealId, status, finalProfit: finalProfit?.toString(), }); return { dealId, state, step0: step0Result, step1: step1Result, step2: step2Result, step3: step3Result, step4: step4Result, riskChecks, redemptionTests, finalProfit, status, }; } catch (error: any) { const durationSeconds = (Date.now() - startTime) / 1000; logger.error('Deal Execution Failed', { dealId, error: error.message, stack: error.stack, }); // Record error metrics metricsService.recordError(error.name || 'UnknownError', state.step); metricsService.recordDealExecution('failed', request.participantBankId, request.moduleId, durationSeconds); // Send alert await alertService.alertDealFailure(dealId, error.message, state.step); // Unregister from risk monitoring riskMonitorService.unregisterDeal(dealId); state.step = DealStep.FAILED; state.errors.push(error.message); state.updatedAt = new Date(); return { dealId, state, riskChecks, redemptionTests, status: 'failed', }; } } async getDealStatus(dealId: string): Promise { return null; } async listDeals(limit: number = 10): Promise { return []; } } export const dealOrchestratorService = new DealOrchestratorService();