Files
27-combi/src/safety/SafetyMonitor.ts
2026-02-09 21:51:30 -08:00

182 lines
5.0 KiB
TypeScript

import { AaveService } from '../aave/AaveService.js';
import { AppConfig } from '../types/index.js';
import { formatUnits } from 'ethers';
export class SafetyMonitor {
constructor(
private aaveService: AaveService,
private config: AppConfig
) {}
/**
* Check if health factor is above minimum threshold
*/
async checkHealthFactor(): Promise<{
isValid: boolean;
healthFactor: number;
message?: string;
}> {
const healthFactor = await this.aaveService.getHealthFactor();
const isValid = healthFactor >= this.config.loop.minHealthFactor;
if (!isValid) {
return {
isValid: false,
healthFactor,
message: `Health factor ${healthFactor.toFixed(4)} is below minimum threshold ${this.config.loop.minHealthFactor}`,
};
}
return {
isValid: true,
healthFactor,
};
}
/**
* Check if loop count is within limits
*/
checkLoopLimit(currentLoop: number): {
isValid: boolean;
message?: string;
} {
if (currentLoop >= this.config.loop.maxLoops) {
return {
isValid: false,
message: `Loop count ${currentLoop} exceeds maximum ${this.config.loop.maxLoops}`,
};
}
return { isValid: true };
}
/**
* Check price deviation (depeg protection)
*/
async checkPriceDeviation(): Promise<{
isValid: boolean;
deviations: Record<string, number>;
message?: string;
}> {
if (!this.config.safety.enablePriceChecks) {
return { isValid: true, deviations: {} };
}
const deviations: Record<string, number> = {};
// Get token addresses
const collateralAddress = this.config.networkConfig.tokens[this.config.loop.collateralAsset];
const borrowAddress = this.config.networkConfig.tokens[this.config.loop.borrowAsset];
if (!collateralAddress || !borrowAddress) {
return { isValid: true, deviations: {} };
}
// In a real implementation, you would:
// 1. Get current prices from oracles (Chainlink, Uniswap, etc.)
// 2. Compare against expected $1.00 for stablecoins
// 3. Check if deviation exceeds threshold
// For now, we'll do a simplified check using the DEX quote
// This is not ideal but demonstrates the concept
try {
// This would require access to the DEX service, so we'll skip for now
// In production, you'd use Chainlink price feeds or similar
return {
isValid: true,
deviations,
};
} catch (error) {
return {
isValid: false,
deviations,
message: `Failed to check price deviation: ${error instanceof Error ? error.message : 'Unknown error'}`,
};
}
}
/**
* Comprehensive pre-execution safety check
*/
async preExecutionCheck(currentLoop: number): Promise<{
isValid: boolean;
checks: {
healthFactor: boolean;
loopLimit: boolean;
priceDeviation: boolean;
};
messages: string[];
}> {
const messages: string[] = [];
const checks = {
healthFactor: true,
loopLimit: true,
priceDeviation: true,
};
// Check health factor
const hfCheck = await this.checkHealthFactor();
if (!hfCheck.isValid) {
checks.healthFactor = false;
messages.push(hfCheck.message || 'Health factor check failed');
}
// Check loop limit
const loopCheck = this.checkLoopLimit(currentLoop);
if (!loopCheck.isValid) {
checks.loopLimit = false;
messages.push(loopCheck.message || 'Loop limit check failed');
}
// Check price deviation
const priceCheck = await this.checkPriceDeviation();
if (!priceCheck.isValid) {
checks.priceDeviation = false;
messages.push(priceCheck.message || 'Price deviation check failed');
}
const isValid = checks.healthFactor && checks.loopLimit && checks.priceDeviation;
return {
isValid,
checks,
messages,
};
}
/**
* Monitor health factor during execution
*/
async monitorHealthFactor(): Promise<number> {
return await this.aaveService.getHealthFactor();
}
/**
* Get current position summary
*/
async getPositionSummary(): Promise<{
totalCollateral: string;
totalDebt: string;
healthFactor: number;
availableBorrow: string;
}> {
const userAddress = await this.aaveService['signer'].getAddress();
const accountData = await this.aaveService.getUserAccountData(userAddress);
// Aave returns values in 8 decimals for base values
const totalCollateral = formatUnits(accountData.totalCollateralBase, 8);
const totalDebt = formatUnits(accountData.totalDebtBase, 8);
const availableBorrow = formatUnits(accountData.availableBorrowsBase, 8);
const healthFactor = Number(formatUnits(accountData.healthFactor, 18));
return {
totalCollateral,
totalDebt,
healthFactor,
availableBorrow,
};
}
}