182 lines
5.0 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|
|
|