Files
defi-arbitrage/deal-orchestrator.service.ts
DBIS Core Team 73e8d30190 chore: sync state and push to Gitea
Made-with: Cursor
2026-03-02 13:17:20 -08:00

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();