236 lines
7.2 KiB
TypeScript
236 lines
7.2 KiB
TypeScript
// 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<DealExecutionResult> {
|
|
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<DealState | null> {
|
|
return null;
|
|
}
|
|
|
|
async listDeals(limit: number = 10): Promise<DealState[]> {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export const dealOrchestratorService = new DealOrchestratorService();
|