mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 21:47:56 +00:00
df58d26893
- Add merchant tier system (Lite/Super/Diamond) with tier badges - Add advanced order filters (token, fiat, payment method, amount range) - Add merchant dashboard with stats, ads management, tier upgrade - Add fraud prevention system with risk scoring and trade limits - Rename migrations to timestamp format for Supabase CLI compatibility - Add new migrations: phase2_phase3_tables, fraud_prevention, merchant_system
372 lines
10 KiB
TypeScript
372 lines
10 KiB
TypeScript
/**
|
|
* P2P Fraud Prevention System
|
|
* Auto-detection rules and risk scoring
|
|
*/
|
|
|
|
// Risk score thresholds
|
|
export const RISK_THRESHOLDS = {
|
|
LOW: 20,
|
|
MEDIUM: 50,
|
|
HIGH: 80,
|
|
CRITICAL: 95
|
|
} as const;
|
|
|
|
// Risk level types
|
|
export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
|
|
// Fraud indicators
|
|
export interface FraudIndicators {
|
|
cancelRate: number; // Percentage of cancelled trades
|
|
disputeRate: number; // Percentage of disputed trades
|
|
avgTradeAmount: number; // Average trade amount
|
|
accountAge: number; // Account age in days
|
|
completedTrades: number; // Total completed trades
|
|
recentCancellations: number; // Cancellations in last 24h
|
|
recentDisputes: number; // Disputes in last 7 days
|
|
paymentNameMismatch: boolean; // Payment account name doesn't match
|
|
rapidTrading: boolean; // Too many trades in short period
|
|
unusualAmount: boolean; // Trade amount significantly higher than usual
|
|
newAccountLargeTrade: boolean; // New account with large trade
|
|
multipleAccounts: boolean; // Suspected multiple accounts
|
|
}
|
|
|
|
// Risk score result
|
|
export interface RiskScoreResult {
|
|
score: number; // 0-100 risk score
|
|
level: RiskLevel; // Risk level classification
|
|
flags: string[]; // Active fraud flags
|
|
recommendations: string[]; // Recommended actions
|
|
autoBlock: boolean; // Should auto-block this user/trade
|
|
requiresReview: boolean; // Should flag for manual review
|
|
}
|
|
|
|
// Fraud detection rules
|
|
export interface FraudRule {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
weight: number; // Score weight (how much this adds to risk)
|
|
check: (indicators: FraudIndicators) => boolean;
|
|
action: 'flag' | 'review' | 'block';
|
|
}
|
|
|
|
// Define fraud detection rules
|
|
export const FRAUD_RULES: FraudRule[] = [
|
|
{
|
|
id: 'high_cancel_rate',
|
|
name: 'High Cancellation Rate',
|
|
description: 'User has cancelled more than 30% of their trades',
|
|
weight: 25,
|
|
check: (ind) => ind.cancelRate > 30,
|
|
action: 'review'
|
|
},
|
|
{
|
|
id: 'frequent_disputes',
|
|
name: 'Frequent Disputes',
|
|
description: 'User has dispute rate higher than 20%',
|
|
weight: 30,
|
|
check: (ind) => ind.disputeRate > 20,
|
|
action: 'review'
|
|
},
|
|
{
|
|
id: 'recent_cancellations',
|
|
name: 'Multiple Recent Cancellations',
|
|
description: 'More than 3 cancellations in the last 24 hours',
|
|
weight: 35,
|
|
check: (ind) => ind.recentCancellations > 3,
|
|
action: 'block'
|
|
},
|
|
{
|
|
id: 'new_account_large_trade',
|
|
name: 'New Account Large Trade',
|
|
description: 'Account less than 7 days old attempting trade over $1000',
|
|
weight: 40,
|
|
check: (ind) => ind.newAccountLargeTrade,
|
|
action: 'review'
|
|
},
|
|
{
|
|
id: 'payment_name_mismatch',
|
|
name: 'Payment Name Mismatch',
|
|
description: 'Payment account name does not match user profile',
|
|
weight: 20,
|
|
check: (ind) => ind.paymentNameMismatch,
|
|
action: 'flag'
|
|
},
|
|
{
|
|
id: 'rapid_trading',
|
|
name: 'Rapid Trading Pattern',
|
|
description: 'Unusually high number of trades in a short period',
|
|
weight: 25,
|
|
check: (ind) => ind.rapidTrading,
|
|
action: 'review'
|
|
},
|
|
{
|
|
id: 'unusual_amount',
|
|
name: 'Unusual Trade Amount',
|
|
description: 'Trade amount significantly higher than user average',
|
|
weight: 15,
|
|
check: (ind) => ind.unusualAmount,
|
|
action: 'flag'
|
|
},
|
|
{
|
|
id: 'no_trading_history',
|
|
name: 'No Trading History',
|
|
description: 'User has no completed trades',
|
|
weight: 10,
|
|
check: (ind) => ind.completedTrades === 0,
|
|
action: 'flag'
|
|
},
|
|
{
|
|
id: 'suspected_multi_account',
|
|
name: 'Suspected Multiple Accounts',
|
|
description: 'Pattern suggests user has multiple accounts',
|
|
weight: 50,
|
|
check: (ind) => ind.multipleAccounts,
|
|
action: 'block'
|
|
},
|
|
{
|
|
id: 'very_new_account',
|
|
name: 'Very New Account',
|
|
description: 'Account created less than 24 hours ago',
|
|
weight: 15,
|
|
check: (ind) => ind.accountAge < 1,
|
|
action: 'flag'
|
|
}
|
|
];
|
|
|
|
/**
|
|
* Calculate risk score based on fraud indicators
|
|
*/
|
|
export function calculateRiskScore(indicators: FraudIndicators): RiskScoreResult {
|
|
let score = 0;
|
|
const flags: string[] = [];
|
|
const recommendations: string[] = [];
|
|
let autoBlock = false;
|
|
let requiresReview = false;
|
|
|
|
// Check each rule
|
|
for (const rule of FRAUD_RULES) {
|
|
if (rule.check(indicators)) {
|
|
score += rule.weight;
|
|
flags.push(rule.name);
|
|
|
|
if (rule.action === 'block') {
|
|
autoBlock = true;
|
|
recommendations.push(`Block: ${rule.description}`);
|
|
} else if (rule.action === 'review') {
|
|
requiresReview = true;
|
|
recommendations.push(`Review: ${rule.description}`);
|
|
} else {
|
|
recommendations.push(`Monitor: ${rule.description}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cap score at 100
|
|
score = Math.min(score, 100);
|
|
|
|
// Determine risk level
|
|
let level: RiskLevel;
|
|
if (score >= RISK_THRESHOLDS.CRITICAL) {
|
|
level = 'critical';
|
|
autoBlock = true;
|
|
} else if (score >= RISK_THRESHOLDS.HIGH) {
|
|
level = 'high';
|
|
requiresReview = true;
|
|
} else if (score >= RISK_THRESHOLDS.MEDIUM) {
|
|
level = 'medium';
|
|
} else {
|
|
level = 'low';
|
|
}
|
|
|
|
return {
|
|
score,
|
|
level,
|
|
flags,
|
|
recommendations,
|
|
autoBlock,
|
|
requiresReview
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Trade limits based on user trust level
|
|
*/
|
|
export const TRADE_LIMITS = {
|
|
new: {
|
|
maxTradeAmount: 100, // Max $100 per trade
|
|
maxDailyTrades: 3, // Max 3 trades per day
|
|
maxDailyVolume: 200, // Max $200 daily volume
|
|
paymentDeadlineMinutes: 30,
|
|
requiresVerification: false
|
|
},
|
|
basic: {
|
|
maxTradeAmount: 500,
|
|
maxDailyTrades: 5,
|
|
maxDailyVolume: 1000,
|
|
paymentDeadlineMinutes: 30,
|
|
requiresVerification: false
|
|
},
|
|
intermediate: {
|
|
maxTradeAmount: 2000,
|
|
maxDailyTrades: 10,
|
|
maxDailyVolume: 5000,
|
|
paymentDeadlineMinutes: 45,
|
|
requiresVerification: true
|
|
},
|
|
advanced: {
|
|
maxTradeAmount: 10000,
|
|
maxDailyTrades: 20,
|
|
maxDailyVolume: 25000,
|
|
paymentDeadlineMinutes: 60,
|
|
requiresVerification: true
|
|
},
|
|
verified: {
|
|
maxTradeAmount: 50000,
|
|
maxDailyTrades: 50,
|
|
maxDailyVolume: 100000,
|
|
paymentDeadlineMinutes: 120,
|
|
requiresVerification: true
|
|
}
|
|
} as const;
|
|
|
|
export type TrustLevel = keyof typeof TRADE_LIMITS;
|
|
|
|
/**
|
|
* Check if a trade is within user's limits
|
|
*/
|
|
export function checkTradeWithinLimits(
|
|
tradeAmount: number,
|
|
trustLevel: TrustLevel,
|
|
todayTradeCount: number,
|
|
todayVolume: number
|
|
): { allowed: boolean; reason?: string } {
|
|
const limits = TRADE_LIMITS[trustLevel];
|
|
|
|
if (tradeAmount > limits.maxTradeAmount) {
|
|
return {
|
|
allowed: false,
|
|
reason: `Trade amount exceeds your limit of $${limits.maxTradeAmount}. Upgrade your trust level for higher limits.`
|
|
};
|
|
}
|
|
|
|
if (todayTradeCount >= limits.maxDailyTrades) {
|
|
return {
|
|
allowed: false,
|
|
reason: `You've reached your daily trade limit of ${limits.maxDailyTrades} trades.`
|
|
};
|
|
}
|
|
|
|
if (todayVolume + tradeAmount > limits.maxDailyVolume) {
|
|
return {
|
|
allowed: false,
|
|
reason: `This trade would exceed your daily volume limit of $${limits.maxDailyVolume}.`
|
|
};
|
|
}
|
|
|
|
return { allowed: true };
|
|
}
|
|
|
|
/**
|
|
* Analyze trade for fraud patterns
|
|
*/
|
|
export function analyzeTradeForFraud(
|
|
tradeAmount: number,
|
|
userAvgTradeAmount: number,
|
|
accountAgeDays: number,
|
|
completedTrades: number
|
|
): Pick<FraudIndicators, 'unusualAmount' | 'newAccountLargeTrade'> {
|
|
// Check if amount is unusual (3x average)
|
|
const unusualAmount = userAvgTradeAmount > 0 && tradeAmount > userAvgTradeAmount * 3;
|
|
|
|
// Check new account with large trade
|
|
const newAccountLargeTrade = accountAgeDays < 7 && tradeAmount > 1000;
|
|
|
|
return {
|
|
unusualAmount,
|
|
newAccountLargeTrade
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Cooldown periods after suspicious activity
|
|
*/
|
|
export const COOLDOWN_PERIODS = {
|
|
afterCancellation: 5 * 60 * 1000, // 5 minutes after cancelling a trade
|
|
afterDispute: 24 * 60 * 60 * 1000, // 24 hours after a dispute
|
|
afterBlock: 7 * 24 * 60 * 60 * 1000, // 7 days after being blocked
|
|
betweenTrades: 1 * 60 * 1000 // 1 minute between accepting trades
|
|
} as const;
|
|
|
|
/**
|
|
* Check if user is in cooldown period
|
|
*/
|
|
export function isInCooldown(
|
|
lastCancellation: Date | null,
|
|
lastDispute: Date | null,
|
|
lastTrade: Date | null
|
|
): { inCooldown: boolean; reason?: string; remainingMs?: number } {
|
|
const now = Date.now();
|
|
|
|
if (lastCancellation) {
|
|
const timeSince = now - lastCancellation.getTime();
|
|
if (timeSince < COOLDOWN_PERIODS.afterCancellation) {
|
|
return {
|
|
inCooldown: true,
|
|
reason: 'Please wait before creating a new trade after cancellation',
|
|
remainingMs: COOLDOWN_PERIODS.afterCancellation - timeSince
|
|
};
|
|
}
|
|
}
|
|
|
|
if (lastDispute) {
|
|
const timeSince = now - lastDispute.getTime();
|
|
if (timeSince < COOLDOWN_PERIODS.afterDispute) {
|
|
return {
|
|
inCooldown: true,
|
|
reason: 'Trading restricted due to recent dispute',
|
|
remainingMs: COOLDOWN_PERIODS.afterDispute - timeSince
|
|
};
|
|
}
|
|
}
|
|
|
|
if (lastTrade) {
|
|
const timeSince = now - lastTrade.getTime();
|
|
if (timeSince < COOLDOWN_PERIODS.betweenTrades) {
|
|
return {
|
|
inCooldown: true,
|
|
reason: 'Please wait a moment before accepting another trade',
|
|
remainingMs: COOLDOWN_PERIODS.betweenTrades - timeSince
|
|
};
|
|
}
|
|
}
|
|
|
|
return { inCooldown: false };
|
|
}
|
|
|
|
/**
|
|
* Reputation penalties for different infractions
|
|
*/
|
|
export const REPUTATION_PENALTIES = {
|
|
tradeCancellation: -5, // Cancelled a trade
|
|
disputeLost: -15, // Lost a dispute
|
|
fraudConfirmed: -50, // Confirmed fraud
|
|
paymentTimeout: -10, // Failed to pay in time
|
|
confirmationTimeout: -3, // Late confirmation (seller)
|
|
reportedByOthers: -8 // Reported by multiple users
|
|
} as const;
|
|
|
|
/**
|
|
* Reputation rewards for good behavior
|
|
*/
|
|
export const REPUTATION_REWARDS = {
|
|
tradeCompleted: 5, // Successfully completed trade
|
|
disputeWon: 10, // Won a dispute (vindicated)
|
|
fastPayment: 2, // Paid within 5 minutes
|
|
fastConfirmation: 2, // Confirmed within 10 minutes
|
|
milestone10Trades: 25, // Completed 10 trades
|
|
milestone50Trades: 50, // Completed 50 trades
|
|
milestone100Trades: 100, // Completed 100 trades
|
|
verifiedMerchant: 200 // Achieved verified merchant status
|
|
} as const;
|