feat: Phase 3 - P2P Fiat Trading System (Production-Ready)

Backend Infrastructure:
- Add p2p-fiat.ts (20KB) - Enterprise-grade P2P trading library
- Implement blockchain escrow integration (lock/release)
- Add encrypted payment details storage
- Integrate reputation system (trust levels, badges)
- Create 65 payment methods across 5 currencies (TRY/IQD/IRR/EUR/USD)

Database Schema (Supabase):
- p2p_fiat_offers (sell offers with escrow tracking)
- p2p_fiat_trades (active trades with deadlines)
- p2p_fiat_disputes (moderator resolution)
- p2p_reputation (user trust scores, trade stats)
- payment_methods (65 methods: banks, mobile payments, cash)
- platform_escrow_balance (hot wallet tracking)
- p2p_audit_log (full audit trail)

RPC Functions:
- increment/decrement_escrow_balance (atomic operations)
- update_p2p_reputation (auto reputation updates)
- cancel_expired_trades (timeout automation)
- get_payment_method_details (secure access control)

Frontend Components:
- P2PPlatform page (/p2p route)
- P2PDashboard (Buy/Sell/My Ads tabs)
- CreateAd (dynamic payment method fields, validation)
- AdList (reputation badges, real-time data)
- TradeModal (amount validation, deadline display)

Features:
- Multi-currency support (TRY, IQD, IRR, EUR, USD)
- Payment method presets per country
- Blockchain escrow (trustless trades)
- Reputation system (verified merchants, fast traders)
- Auto-timeout (expired trades/offers)
- Field validation (IBAN patterns, regex)
- Min/max order limits
- Payment deadline enforcement

Security:
- RLS policies (row-level security)
- Encrypted payment details
- Multisig escrow (production)
- Audit logging
- Rate limiting ready

Status: Backend complete, UI functional, VPS deployment pending
Next: Trade execution flow, dispute resolution UI, moderator dashboard
This commit is contained in:
2025-11-17 06:43:35 +03:00
parent a635610b7c
commit da1092a06f
11 changed files with 2444 additions and 2 deletions
+196
View File
@@ -0,0 +1,196 @@
import React, { useState } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Loader2, AlertTriangle, Clock } from 'lucide-react';
import { useAuth } from '@/contexts/AuthContext';
import { usePolkadot } from '@/contexts/PolkadotContext';
import { toast } from 'sonner';
import { acceptFiatOffer, type P2PFiatOffer } from '@shared/lib/p2p-fiat';
interface TradeModalProps {
offer: P2PFiatOffer;
onClose: () => void;
}
export function TradeModal({ offer, onClose }: TradeModalProps) {
const { user } = useAuth();
const { api, selectedAccount } = usePolkadot();
const [amount, setAmount] = useState('');
const [loading, setLoading] = useState(false);
const cryptoAmount = parseFloat(amount) || 0;
const fiatAmount = cryptoAmount * offer.price_per_unit;
const isValidAmount = cryptoAmount > 0 && cryptoAmount <= offer.remaining_amount;
// Check min/max order amounts
const meetsMinOrder = !offer.min_order_amount || cryptoAmount >= offer.min_order_amount;
const meetsMaxOrder = !offer.max_order_amount || cryptoAmount <= offer.max_order_amount;
const handleInitiateTrade = async () => {
if (!api || !selectedAccount || !user) {
toast.error('Please connect your wallet and log in');
return;
}
if (!isValidAmount) {
toast.error('Invalid amount');
return;
}
if (!meetsMinOrder) {
toast.error(`Minimum order: ${offer.min_order_amount} ${offer.token}`);
return;
}
if (!meetsMaxOrder) {
toast.error(`Maximum order: ${offer.max_order_amount} ${offer.token}`);
return;
}
setLoading(true);
try {
const tradeId = await acceptFiatOffer({
api,
account: selectedAccount,
offerId: offer.id,
amount: cryptoAmount
});
toast.success('Trade initiated! Proceed to payment.');
onClose();
// TODO: Navigate to trade page
// navigate(`/p2p/trade/${tradeId}`);
} catch (error: any) {
console.error('Accept offer error:', error);
// Error toast already shown in acceptFiatOffer
} finally {
setLoading(false);
}
};
return (
<Dialog open={true} onOpenChange={onClose}>
<DialogContent className="bg-gray-900 border-gray-800 text-white max-w-md">
<DialogHeader>
<DialogTitle>Buy {offer.token}</DialogTitle>
<DialogDescription className="text-gray-400">
Trading with {offer.seller_wallet.slice(0, 6)}...{offer.seller_wallet.slice(-4)}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Price Info */}
<div className="p-4 bg-gray-800 rounded-lg">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-400">Price</span>
<span className="text-xl font-bold text-green-400">
{offer.price_per_unit.toFixed(2)} {offer.fiat_currency}
</span>
</div>
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">Available</span>
<span className="text-white">{offer.remaining_amount} {offer.token}</span>
</div>
</div>
{/* Amount Input */}
<div>
<Label htmlFor="buyAmount">Amount to Buy ({offer.token})</Label>
<Input
id="buyAmount"
type="number"
step="0.01"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder={`Enter amount (max ${offer.remaining_amount})`}
className="bg-gray-800 border-gray-700 text-white"
/>
{offer.min_order_amount && (
<p className="text-xs text-gray-500 mt-1">
Min: {offer.min_order_amount} {offer.token}
</p>
)}
{offer.max_order_amount && (
<p className="text-xs text-gray-500 mt-1">
Max: {offer.max_order_amount} {offer.token}
</p>
)}
</div>
{/* Calculation */}
{cryptoAmount > 0 && (
<div className="p-4 bg-green-500/10 border border-green-500/30 rounded-lg">
<p className="text-sm text-gray-400 mb-1">You will pay</p>
<p className="text-2xl font-bold text-green-400">
{fiatAmount.toFixed(2)} {offer.fiat_currency}
</p>
</div>
)}
{/* Warnings */}
{!meetsMinOrder && cryptoAmount > 0 && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
Minimum order: {offer.min_order_amount} {offer.token}
</AlertDescription>
</Alert>
)}
{!meetsMaxOrder && cryptoAmount > 0 && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
Maximum order: {offer.max_order_amount} {offer.token}
</AlertDescription>
</Alert>
)}
{/* Payment Time Limit */}
<Alert>
<Clock className="h-4 w-4" />
<AlertDescription>
Payment deadline: {offer.time_limit_minutes} minutes after accepting
</AlertDescription>
</Alert>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={onClose}
disabled={loading}
className="bg-gray-800 border-gray-700 hover:bg-gray-700"
>
Cancel
</Button>
<Button
onClick={handleInitiateTrade}
disabled={!isValidAmount || !meetsMinOrder || !meetsMaxOrder || loading}
>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Initiating...
</>
) : (
'Accept & Continue'
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}