Files
pwap/web/supabase/migrations/20241117054602_p2p_rpc_functions.sql
T
pezkuwichain 8d30519efc Fix all shadow deprecation warnings across entire mobile app
- Replaced shadowColor/shadowOffset/shadowOpacity/shadowRadius with boxShadow
- Fixed 28 files (21 screens + 7 components)
- Preserved elevation for Android compatibility
- All React Native Web deprecation warnings resolved

Files fixed:
- All screen components
- All reusable components
- Navigation components
- Modal components
2026-01-14 15:05:10 +03:00

311 lines
9.4 KiB
PL/PgSQL

-- =====================================================
-- P2P FIAT SYSTEM - RPC FUNCTIONS
-- Production-grade stored procedures
-- =====================================================
-- =====================================================
-- INCREMENT ESCROW BALANCE
-- =====================================================
CREATE OR REPLACE FUNCTION public.increment_escrow_balance(
p_token TEXT,
p_amount NUMERIC
) RETURNS void AS $$
BEGIN
UPDATE public.platform_escrow_balance
SET
total_locked = total_locked + p_amount,
updated_at = NOW()
WHERE token = p_token;
IF NOT FOUND THEN
RAISE EXCEPTION 'Token % not found in escrow balance', p_token;
END IF;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- DECREMENT ESCROW BALANCE
-- =====================================================
CREATE OR REPLACE FUNCTION public.decrement_escrow_balance(
p_token TEXT,
p_amount NUMERIC
) RETURNS void AS $$
BEGIN
UPDATE public.platform_escrow_balance
SET
total_locked = total_locked - p_amount,
updated_at = NOW()
WHERE token = p_token;
IF NOT FOUND THEN
RAISE EXCEPTION 'Token % not found in escrow balance', p_token;
END IF;
-- Check for negative balance (should never happen)
IF (SELECT total_locked FROM public.platform_escrow_balance WHERE token = p_token) < 0 THEN
RAISE EXCEPTION 'Escrow balance would go negative for token %', p_token;
END IF;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- UPDATE P2P REPUTATION AFTER TRADE
-- =====================================================
CREATE OR REPLACE FUNCTION public.update_p2p_reputation(
p_seller_id UUID,
p_buyer_id UUID,
p_trade_id UUID
) RETURNS void AS $$
DECLARE
v_trade RECORD;
v_payment_time_minutes INT;
v_confirmation_time_minutes INT;
BEGIN
-- Get trade details
SELECT * INTO v_trade
FROM public.p2p_fiat_trades
WHERE id = p_trade_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Trade % not found', p_trade_id;
END IF;
-- Calculate timing metrics
IF v_trade.buyer_marked_paid_at IS NOT NULL THEN
v_payment_time_minutes := EXTRACT(EPOCH FROM (v_trade.buyer_marked_paid_at - v_trade.created_at)) / 60;
END IF;
IF v_trade.seller_confirmed_at IS NOT NULL AND v_trade.buyer_marked_paid_at IS NOT NULL THEN
v_confirmation_time_minutes := EXTRACT(EPOCH FROM (v_trade.seller_confirmed_at - v_trade.buyer_marked_paid_at)) / 60;
END IF;
-- Update seller reputation
INSERT INTO public.p2p_reputation (
user_id,
total_trades,
completed_trades,
total_as_seller,
reputation_score,
avg_confirmation_time_minutes,
last_trade_at,
first_trade_at
) VALUES (
p_seller_id,
1,
1,
1,
105, -- +5 bonus for first trade
v_confirmation_time_minutes,
NOW(),
NOW()
)
ON CONFLICT (user_id) DO UPDATE SET
total_trades = p2p_reputation.total_trades + 1,
completed_trades = p2p_reputation.completed_trades + 1,
total_as_seller = p2p_reputation.total_as_seller + 1,
reputation_score = LEAST(p2p_reputation.reputation_score + 5, 1000),
avg_confirmation_time_minutes = CASE
WHEN p2p_reputation.avg_confirmation_time_minutes IS NULL THEN v_confirmation_time_minutes
ELSE (p2p_reputation.avg_confirmation_time_minutes + COALESCE(v_confirmation_time_minutes, 0)) / 2
END,
last_trade_at = NOW(),
updated_at = NOW();
-- Update buyer reputation
INSERT INTO public.p2p_reputation (
user_id,
total_trades,
completed_trades,
total_as_buyer,
reputation_score,
avg_payment_time_minutes,
last_trade_at,
first_trade_at
) VALUES (
p_buyer_id,
1,
1,
1,
105,
v_payment_time_minutes,
NOW(),
NOW()
)
ON CONFLICT (user_id) DO UPDATE SET
total_trades = p2p_reputation.total_trades + 1,
completed_trades = p2p_reputation.completed_trades + 1,
total_as_buyer = p2p_reputation.total_as_buyer + 1,
reputation_score = LEAST(p2p_reputation.reputation_score + 5, 1000),
avg_payment_time_minutes = CASE
WHEN p2p_reputation.avg_payment_time_minutes IS NULL THEN v_payment_time_minutes
ELSE (p2p_reputation.avg_payment_time_minutes + COALESCE(v_payment_time_minutes, 0)) / 2
END,
last_trade_at = NOW(),
updated_at = NOW();
-- Update trust levels based on reputation score
UPDATE public.p2p_reputation
SET trust_level = CASE
WHEN reputation_score >= 900 THEN 'verified'
WHEN reputation_score >= 700 THEN 'advanced'
WHEN reputation_score >= 400 THEN 'intermediate'
WHEN reputation_score >= 100 THEN 'basic'
ELSE 'new'
END,
fast_trader = CASE
WHEN avg_payment_time_minutes < 15 AND avg_confirmation_time_minutes < 30 THEN true
ELSE false
END
WHERE user_id IN (p_seller_id, p_buyer_id);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- CANCEL EXPIRED TRADES (Cron job function)
-- =====================================================
CREATE OR REPLACE FUNCTION public.cancel_expired_trades()
RETURNS void AS $$
DECLARE
v_trade RECORD;
BEGIN
-- Cancel trades where buyer didn't pay in time
FOR v_trade IN
SELECT * FROM public.p2p_fiat_trades
WHERE status = 'pending'
AND payment_deadline < NOW()
LOOP
-- Update trade status
UPDATE public.p2p_fiat_trades
SET
status = 'cancelled',
cancelled_by = seller_id,
cancellation_reason = 'Payment deadline expired',
updated_at = NOW()
WHERE id = v_trade.id;
-- Restore offer remaining amount
UPDATE public.p2p_fiat_offers
SET
remaining_amount = remaining_amount + v_trade.crypto_amount,
status = CASE
WHEN status = 'locked' THEN 'open'
ELSE status
END,
updated_at = NOW()
WHERE id = v_trade.offer_id;
-- Update reputation (penalty for buyer)
UPDATE public.p2p_reputation
SET
cancelled_trades = cancelled_trades + 1,
reputation_score = GREATEST(reputation_score - 10, 0),
updated_at = NOW()
WHERE user_id = v_trade.buyer_id;
END LOOP;
-- Auto-release trades where seller didn't confirm in time
FOR v_trade IN
SELECT * FROM public.p2p_fiat_trades
WHERE status = 'payment_sent'
AND confirmation_deadline < NOW()
LOOP
-- Mark as completed (auto-release)
UPDATE public.p2p_fiat_trades
SET
seller_confirmed_at = NOW(),
status = 'completed',
completed_at = NOW(),
updated_at = NOW()
WHERE id = v_trade.id;
-- Note: Actual blockchain release must be done by backend service
-- This just marks the trade as ready for release
-- Update reputations
PERFORM public.update_p2p_reputation(v_trade.seller_id, v_trade.buyer_id, v_trade.id);
END LOOP;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- CANCEL EXPIRED OFFERS
-- =====================================================
CREATE OR REPLACE FUNCTION public.cancel_expired_offers()
RETURNS void AS $$
BEGIN
UPDATE public.p2p_fiat_offers
SET
status = 'cancelled',
updated_at = NOW()
WHERE status = 'open'
AND expires_at < NOW();
-- Note: Escrow refunds must be processed by backend service
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- GET PAYMENT METHOD DETAILS
-- =====================================================
CREATE OR REPLACE FUNCTION public.get_payment_method_details(
p_offer_id UUID,
p_requesting_user_id UUID
) RETURNS TABLE(
method_name TEXT,
payment_details JSONB
) AS $$
DECLARE
v_offer RECORD;
v_trade RECORD;
BEGIN
-- Get offer
SELECT * INTO v_offer
FROM public.p2p_fiat_offers
WHERE id = p_offer_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Offer not found';
END IF;
-- Check if user is involved in an active trade for this offer
SELECT * INTO v_trade
FROM public.p2p_fiat_trades
WHERE offer_id = p_offer_id
AND buyer_id = p_requesting_user_id
AND status IN ('pending', 'payment_sent')
LIMIT 1;
IF NOT FOUND THEN
RAISE EXCEPTION 'Unauthorized: You must have an active trade to view payment details';
END IF;
-- Return decrypted payment details
RETURN QUERY
SELECT
pm.method_name,
CASE
WHEN v_offer.payment_details_encrypted IS NOT NULL THEN
pgsodium.crypto_aead_aes256gcm_decrypt(
pgsodium.url_decode((v_offer.payment_details_encrypted->>'ciphertext')::TEXT),
NULL, -- No associated data used in this context
pgsodium.url_decode((v_offer.payment_details_encrypted->>'nonce')::TEXT),
pg_catalog.get_secret_variable('app.app_encryption_key')::BYTEA
)::TEXT::JSONB
ELSE
NULL
END AS payment_details
FROM public.payment_methods pm
WHERE pm.id = v_offer.payment_method_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- GRANT EXECUTE PERMISSIONS
-- =====================================================
GRANT EXECUTE ON FUNCTION public.increment_escrow_balance TO authenticated;
GRANT EXECUTE ON FUNCTION public.decrement_escrow_balance TO authenticated;
GRANT EXECUTE ON FUNCTION public.update_p2p_reputation TO authenticated;
GRANT EXECUTE ON FUNCTION public.cancel_expired_trades TO authenticated;
GRANT EXECUTE ON FUNCTION public.cancel_expired_offers TO authenticated;
GRANT EXECUTE ON FUNCTION public.get_payment_method_details TO authenticated;