Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/modules/security-posture/enums/asset-risk.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum AssetRiskSeverity {
LOW = 'LOW',
MEDIUM = 'MEDIUM',
HIGH = 'HIGH',
CRITICAL = 'CRITICAL',
}
25 changes: 25 additions & 0 deletions src/modules/security-posture/factor-calculator.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Injectable } from '@nestjs/common';
import { AssetDrainDetector } from './utils/asset-drain.detector';
import { AssetTransferEvent } from './interfaces/asset-drain.interface';
import { FactorInputMetrics } from './interfaces/posture-factor.interface';

@Injectable()
export class FactorCalculatorService {
constructor(private readonly drainDetector: AssetDrainDetector) {}

/**
* Resolves transfer streams into unified factor metrics readable by the scoring engine
*/
public compileAssetDrainMetrics(
transfers: AssetTransferEvent[],
currentTreasuryBalance: number,
): FactorInputMetrics {
const assessment = this.drainDetector.evaluateOutflows(transfers, currentTreasuryBalance);

return {
factorId: 'rapid_asset_drain',
rawCount: assessment.isDraining ? 1 : 0,
severityMultiplier: assessment.deductionScore, // Directly injects dynamic deduction severity
};
}
}
18 changes: 18 additions & 0 deletions src/modules/security-posture/interfaces/asset-drain.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// src/modules/security-posture/interfaces/asset-drain.interface.ts
import { AssetRiskSeverity } from '../enums/asset-risk.enum';

export interface AssetTransferEvent {
transactionHash: string;
tokenAddress: string;
amount: number; // Normalized decimal value
usdValue: number; // Value at time of execution
timestamp: Date;
}

export interface DrainageEvaluationResult {
isDraining: boolean;
severity: AssetRiskSeverity;
totalValueLostUsd: number;
percentageDrop: number;
deductionScore: number;
}
59 changes: 59 additions & 0 deletions src/modules/security-posture/utils/asset-drain.detector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// src/modules/security-posture/utils/asset-drain.detector.ts
import { Injectable } from '@nestjs/common';
import { AssetRiskSeverity } from '../enums/asset-risk.enum';
import { AssetTransferEvent, DrainageEvaluationResult } from '../interfaces/asset-drain.interface';

@Injectable()
export class AssetDrainDetector {
// Configurations for rapid draining thresholds
private readonly DETECTION_WINDOW_MS = 5 * 60 * 1000; // 5-minute rolling window
private readonly CRITICAL_VALUE_THRESHOLD_USD = 50000; // $50k outflow drops within window triggers alarm

/**
* Processes transfer events deterministically to identify rapid draining anomalies.
*/
public evaluateOutflows(
transfers: AssetTransferEvent[],
startingTreasuryValueUsd: number,
): DrainageEvaluationResult {
const now = Date.now();
const activeWindowStart = now - this.DETECTION_WINDOW_MS;

// Filter outflows that occurred strictly within the active evaluation window
const recentTransfers = transfers.filter(
t => new Date(t.timestamp).getTime() >= activeWindowStart,
);

const totalValueLostUsd = recentTransfers.reduce((sum, t) => sum + t.usdValue, 0);
const percentageDrop =
startingTreasuryValueUsd > 0 ? (totalValueLostUsd / startingTreasuryValueUsd) * 100 : 0;

// Trigger classification thresholds
const isDraining =
totalValueLostUsd >= this.CRITICAL_VALUE_THRESHOLD_USD || percentageDrop >= 20;

let severity = AssetRiskSeverity.LOW;
let deductionScore = 0;

if (isDraining) {
if (percentageDrop >= 50 || totalValueLostUsd >= 250000) {
severity = AssetRiskSeverity.CRITICAL;
deductionScore = 40; // Max penalty contribution to security posture
} else {
severity = AssetRiskSeverity.HIGH;
deductionScore = 25;
}
} else if (totalValueLostUsd > this.CRITICAL_VALUE_THRESHOLD_USD * 0.5) {
severity = AssetRiskSeverity.MEDIUM;
deductionScore = 10;
}

return {
isDraining,
severity,
totalValueLostUsd,
percentageDrop: Math.round(percentageDrop * 100) / 100,
deductionScore,
};
}
}
Loading