feat: add full p2p-fiat library with escrow support

- Copy complete p2p-fiat.ts from pwap/shared with all functions
- Add createFiatOffer with internal ledger escrow lock
- Add AES-256-GCM encryption for payment details
- Add audit logging, reputation management
- Update CreateAd.tsx to use createFiatOffer instead of direct insert
- Remove unused wallet context from CreateAd
This commit is contained in:
Kurdistan Tech Ministry committed 2026-02-03 12:17:33 +03:00
1 parent 8676e947a5
commit 4074e7a884
2 files changed
+747 -314

No files matched your search

+14 -55
View File
@@ -1,17 +1,15 @@
import React, { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { useWallet } from '@/contexts/WalletContext';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { toast } from 'sonner';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
import { supabase } from '@/lib/supabase';
import { import {
getPaymentMethods, getPaymentMethods,
validatePaymentDetails, validatePaymentDetails,
createFiatOffer,
type PaymentMethod, type PaymentMethod,
type FiatCurrency, type FiatCurrency,
type CryptoToken type CryptoToken
@@ -23,7 +21,6 @@ interface CreateAdProps {
export function CreateAd({ onAdCreated }: CreateAdProps) { export function CreateAd({ onAdCreated }: CreateAdProps) {
const { user } = useAuth(); const { user } = useAuth();
const { address } = useWallet();
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]); const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod | null>(null); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod | null>(null);
@@ -75,16 +72,11 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
}; };
const handleCreateAd = async () => { const handleCreateAd = async () => {
console.log('🔥 handleCreateAd called', { address, user: user?.id }); if (!user) {
if (!address || !user) {
toast.error('Please connect your wallet and log in');
console.log('❌ No address or user', { address, user });
return; return;
} }
if (!selectedPaymentMethod) { if (!selectedPaymentMethod) {
toast.error('Please select a payment method');
return; return;
} }
@@ -95,8 +87,6 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
); );
if (!validation.valid) { if (!validation.valid) {
const firstError = Object.values(validation.errors)[0];
toast.error(firstError);
return; return;
} }
@@ -105,63 +95,32 @@ export function CreateAd({ onAdCreated }: CreateAdProps) {
const fiatAmt = parseFloat(fiatAmount); const fiatAmt = parseFloat(fiatAmount);
if (!cryptoAmt || cryptoAmt <= 0) { if (!cryptoAmt || cryptoAmt <= 0) {
toast.error('Invalid crypto amount');
return; return;
} }
if (!fiatAmt || fiatAmt <= 0) { if (!fiatAmt || fiatAmt <= 0) {
toast.error('Invalid fiat amount');
return;
}
if (selectedPaymentMethod.min_trade_amount && fiatAmt < selectedPaymentMethod.min_trade_amount) {
toast.error(`Minimum trade amount: ${selectedPaymentMethod.min_trade_amount} ${fiatCurrency}`);
return;
}
if (selectedPaymentMethod.max_trade_amount && fiatAmt > selectedPaymentMethod.max_trade_amount) {
toast.error(`Maximum trade amount: ${selectedPaymentMethod.max_trade_amount} ${fiatCurrency}`);
return; return;
} }
setLoading(true); setLoading(true);
try { try {
// Insert offer into Supabase // Use createFiatOffer which handles escrow locking via internal ledger
// Note: payment_details_encrypted is stored as JSON string (encryption handled server-side in prod) await createFiatOffer({
const { data, error } = await supabase
.from('p2p_fiat_offers')
.insert({
seller_id: user.id,
seller_wallet: address,
ad_type: adType,
token, token,
amount_crypto: cryptoAmt, amountCrypto: cryptoAmt,
remaining_amount: cryptoAmt, fiatCurrency,
fiat_currency: fiatCurrency, fiatAmount: fiatAmt,
fiat_amount: fiatAmt, paymentMethodId: selectedPaymentMethod.id,
payment_method_id: selectedPaymentMethod.id, paymentDetails,
payment_details_encrypted: JSON.stringify(paymentDetails), timeLimitMinutes: timeLimit,
time_limit_minutes: timeLimit, minOrderAmount: minOrderAmount ? parseFloat(minOrderAmount) : undefined,
min_order_amount: minOrderAmount ? parseFloat(minOrderAmount) : null, maxOrderAmount: maxOrderAmount ? parseFloat(maxOrderAmount) : undefined,
max_order_amount: maxOrderAmount ? parseFloat(maxOrderAmount) : null, });
status: 'open'
})
.select()
.single();
if (error) {
console.error('❌ Supabase error:', error);
toast.error(error.message || 'Failed to create offer');
return;
}
console.log('✅ Offer created successfully:', data);
toast.success('Ad created successfully!');
onAdCreated(); onAdCreated();
} catch (error) { } catch (error) {
if (import.meta.env.DEV) console.error('Create ad error:', error); if (import.meta.env.DEV) console.error('Create ad error:', error);
toast.error('Failed to create offer');
} finally { } finally {
setLoading(false); setLoading(false);
} }
+733 -259
View File
File diff suppressed because it is too large. Load diff