mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 21:47:56 +00:00
14f5e84d15
Phase 5 implementation - Internal Ledger Escrow (OKX Model): - No blockchain transactions during P2P trades - Blockchain tx only at deposit/withdraw - Fast and fee-free P2P trading Database: - Add user_internal_balances table - Add p2p_deposit_withdraw_requests table - Add p2p_balance_transactions table - Add lock_escrow_internal(), release_escrow_internal() functions - Add process_deposit(), request_withdraw() functions UI Components: - Add InternalBalanceCard showing available/locked balances - Add DepositModal for crypto deposits to P2P balance - Add WithdrawModal for withdrawals from P2P balance - Integrate balance card into P2PDashboard Backend: - Add process-withdrawal Edge Function - Add verify-deposit Edge Function Updated p2p-fiat.ts: - createFiatOffer() uses internal balance lock - confirmPaymentReceived() uses internal balance transfer - Add internal balance management functions
548 lines
18 KiB
PL/PgSQL
548 lines
18 KiB
PL/PgSQL
-- =====================================================
|
|
-- P2P INTERNAL LEDGER ESCROW SYSTEM (OKX Model)
|
|
-- Migration: 014_p2p_internal_ledger_escrow.sql
|
|
-- Date: 2025-12-11
|
|
-- =====================================================
|
|
--
|
|
-- This migration implements OKX-style internal ledger escrow where:
|
|
-- - Blockchain transactions ONLY occur at deposit/withdraw
|
|
-- - P2P trades use internal database balance transfers
|
|
-- - No blockchain transactions during actual P2P trading
|
|
--
|
|
|
|
-- =====================================================
|
|
-- 1. USER INTERNAL BALANCES TABLE
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS user_internal_balances (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
token TEXT NOT NULL CHECK (token IN ('HEZ', 'PEZ')),
|
|
available_balance DECIMAL(20, 12) NOT NULL DEFAULT 0 CHECK (available_balance >= 0),
|
|
locked_balance DECIMAL(20, 12) NOT NULL DEFAULT 0 CHECK (locked_balance >= 0),
|
|
total_deposited DECIMAL(20, 12) NOT NULL DEFAULT 0,
|
|
total_withdrawn DECIMAL(20, 12) NOT NULL DEFAULT 0,
|
|
last_deposit_at TIMESTAMPTZ,
|
|
last_withdraw_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(user_id, token)
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX IF NOT EXISTS idx_internal_balances_user ON user_internal_balances(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_internal_balances_token ON user_internal_balances(token);
|
|
|
|
-- RLS
|
|
ALTER TABLE user_internal_balances ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Users can view their own balances
|
|
CREATE POLICY "Users can view own balances"
|
|
ON user_internal_balances FOR SELECT
|
|
USING (user_id = auth.uid());
|
|
|
|
-- Only system can modify balances (via service role)
|
|
-- No INSERT/UPDATE/DELETE policies for regular users
|
|
|
|
-- Admins can view all balances
|
|
CREATE POLICY "Admins can view all balances"
|
|
ON user_internal_balances FOR SELECT
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM profiles
|
|
WHERE profiles.id = auth.uid()
|
|
AND profiles.role IN ('admin', 'super_admin')
|
|
)
|
|
);
|
|
|
|
-- =====================================================
|
|
-- 2. DEPOSIT/WITHDRAW REQUESTS TABLE
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS p2p_deposit_withdraw_requests (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
request_type TEXT NOT NULL CHECK (request_type IN ('deposit', 'withdraw')),
|
|
token TEXT NOT NULL CHECK (token IN ('HEZ', 'PEZ')),
|
|
amount DECIMAL(20, 12) NOT NULL CHECK (amount > 0),
|
|
wallet_address TEXT NOT NULL,
|
|
blockchain_tx_hash TEXT,
|
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed', 'cancelled')),
|
|
processed_at TIMESTAMPTZ,
|
|
processed_by UUID REFERENCES auth.users(id),
|
|
error_message TEXT,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX IF NOT EXISTS idx_deposit_withdraw_status ON p2p_deposit_withdraw_requests(status);
|
|
CREATE INDEX IF NOT EXISTS idx_deposit_withdraw_user ON p2p_deposit_withdraw_requests(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_deposit_withdraw_type ON p2p_deposit_withdraw_requests(request_type);
|
|
|
|
-- RLS
|
|
ALTER TABLE p2p_deposit_withdraw_requests ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Users can view their own requests
|
|
CREATE POLICY "Users can view own requests"
|
|
ON p2p_deposit_withdraw_requests FOR SELECT
|
|
USING (user_id = auth.uid());
|
|
|
|
-- Users can create their own requests
|
|
CREATE POLICY "Users can create own requests"
|
|
ON p2p_deposit_withdraw_requests FOR INSERT
|
|
WITH CHECK (user_id = auth.uid());
|
|
|
|
-- Admins can view and manage all requests
|
|
CREATE POLICY "Admins can manage all requests"
|
|
ON p2p_deposit_withdraw_requests FOR ALL
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM profiles
|
|
WHERE profiles.id = auth.uid()
|
|
AND profiles.role IN ('admin', 'super_admin')
|
|
)
|
|
);
|
|
|
|
-- =====================================================
|
|
-- 3. BALANCE TRANSACTION LOG (Audit Trail)
|
|
-- =====================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS p2p_balance_transactions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
token TEXT NOT NULL,
|
|
transaction_type TEXT NOT NULL CHECK (transaction_type IN (
|
|
'deposit', 'withdraw', 'escrow_lock', 'escrow_release',
|
|
'escrow_refund', 'trade_receive', 'admin_adjustment'
|
|
)),
|
|
amount DECIMAL(20, 12) NOT NULL,
|
|
balance_before DECIMAL(20, 12) NOT NULL,
|
|
balance_after DECIMAL(20, 12) NOT NULL,
|
|
reference_type TEXT, -- 'offer', 'trade', 'deposit_request', 'withdraw_request'
|
|
reference_id UUID,
|
|
description TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX IF NOT EXISTS idx_balance_tx_user ON p2p_balance_transactions(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_balance_tx_type ON p2p_balance_transactions(transaction_type);
|
|
CREATE INDEX IF NOT EXISTS idx_balance_tx_created ON p2p_balance_transactions(created_at DESC);
|
|
|
|
-- RLS
|
|
ALTER TABLE p2p_balance_transactions ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Users can view their own transaction history
|
|
CREATE POLICY "Users can view own transactions"
|
|
ON p2p_balance_transactions FOR SELECT
|
|
USING (user_id = auth.uid());
|
|
|
|
-- =====================================================
|
|
-- 4. LOCK ESCROW FUNCTION (Internal Balance)
|
|
-- =====================================================
|
|
|
|
CREATE OR REPLACE FUNCTION lock_escrow_internal(
|
|
p_user_id UUID,
|
|
p_token TEXT,
|
|
p_amount DECIMAL(20, 12),
|
|
p_reference_type TEXT DEFAULT NULL,
|
|
p_reference_id UUID DEFAULT NULL
|
|
) RETURNS JSON AS $$
|
|
DECLARE
|
|
v_balance RECORD;
|
|
v_balance_before DECIMAL(20, 12);
|
|
BEGIN
|
|
-- Lock user's balance row for update
|
|
SELECT * INTO v_balance
|
|
FROM user_internal_balances
|
|
WHERE user_id = p_user_id AND token = p_token
|
|
FOR UPDATE;
|
|
|
|
-- Check if balance exists
|
|
IF v_balance IS NULL THEN
|
|
RETURN json_build_object(
|
|
'success', false,
|
|
'error', 'No balance found for token ' || p_token || '. Please deposit first.'
|
|
);
|
|
END IF;
|
|
|
|
v_balance_before := v_balance.available_balance;
|
|
|
|
-- Check sufficient balance
|
|
IF v_balance.available_balance < p_amount THEN
|
|
RETURN json_build_object(
|
|
'success', false,
|
|
'error', 'Insufficient balance. Available: ' || v_balance.available_balance || ' ' || p_token
|
|
);
|
|
END IF;
|
|
|
|
-- Move from available to locked
|
|
UPDATE user_internal_balances
|
|
SET
|
|
available_balance = available_balance - p_amount,
|
|
locked_balance = locked_balance + p_amount,
|
|
updated_at = NOW()
|
|
WHERE user_id = p_user_id AND token = p_token;
|
|
|
|
-- Log the transaction
|
|
INSERT INTO p2p_balance_transactions (
|
|
user_id, token, transaction_type, amount,
|
|
balance_before, balance_after, reference_type, reference_id,
|
|
description
|
|
) VALUES (
|
|
p_user_id, p_token, 'escrow_lock', p_amount,
|
|
v_balance_before, v_balance_before - p_amount, p_reference_type, p_reference_id,
|
|
'Escrow locked for P2P offer'
|
|
);
|
|
|
|
RETURN json_build_object(
|
|
'success', true,
|
|
'locked_amount', p_amount,
|
|
'available_balance', v_balance_before - p_amount,
|
|
'locked_balance', v_balance.locked_balance + p_amount
|
|
);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- =====================================================
|
|
-- 5. RELEASE ESCROW FUNCTION (Trade Completion)
|
|
-- =====================================================
|
|
|
|
CREATE OR REPLACE FUNCTION release_escrow_internal(
|
|
p_from_user_id UUID,
|
|
p_to_user_id UUID,
|
|
p_token TEXT,
|
|
p_amount DECIMAL(20, 12),
|
|
p_reference_type TEXT DEFAULT 'trade',
|
|
p_reference_id UUID DEFAULT NULL
|
|
) RETURNS JSON AS $$
|
|
DECLARE
|
|
v_from_balance RECORD;
|
|
v_to_balance RECORD;
|
|
v_from_balance_before DECIMAL(20, 12);
|
|
v_to_balance_before DECIMAL(20, 12);
|
|
BEGIN
|
|
-- Lock seller's balance row
|
|
SELECT * INTO v_from_balance
|
|
FROM user_internal_balances
|
|
WHERE user_id = p_from_user_id AND token = p_token
|
|
FOR UPDATE;
|
|
|
|
-- Check seller has sufficient locked balance
|
|
IF v_from_balance IS NULL OR v_from_balance.locked_balance < p_amount THEN
|
|
RETURN json_build_object(
|
|
'success', false,
|
|
'error', 'Insufficient locked balance for release'
|
|
);
|
|
END IF;
|
|
|
|
v_from_balance_before := v_from_balance.locked_balance;
|
|
|
|
-- Reduce seller's locked balance
|
|
UPDATE user_internal_balances
|
|
SET
|
|
locked_balance = locked_balance - p_amount,
|
|
updated_at = NOW()
|
|
WHERE user_id = p_from_user_id AND token = p_token;
|
|
|
|
-- Log seller's transaction
|
|
INSERT INTO p2p_balance_transactions (
|
|
user_id, token, transaction_type, amount,
|
|
balance_before, balance_after, reference_type, reference_id,
|
|
description
|
|
) VALUES (
|
|
p_from_user_id, p_token, 'escrow_release', -p_amount,
|
|
v_from_balance_before, v_from_balance_before - p_amount, p_reference_type, p_reference_id,
|
|
'Escrow released to buyer'
|
|
);
|
|
|
|
-- Get or initialize buyer's balance
|
|
SELECT available_balance INTO v_to_balance_before
|
|
FROM user_internal_balances
|
|
WHERE user_id = p_to_user_id AND token = p_token;
|
|
|
|
IF v_to_balance_before IS NULL THEN
|
|
v_to_balance_before := 0;
|
|
END IF;
|
|
|
|
-- Increase buyer's available balance (upsert)
|
|
INSERT INTO user_internal_balances (user_id, token, available_balance)
|
|
VALUES (p_to_user_id, p_token, p_amount)
|
|
ON CONFLICT (user_id, token)
|
|
DO UPDATE SET
|
|
available_balance = user_internal_balances.available_balance + p_amount,
|
|
updated_at = NOW();
|
|
|
|
-- Log buyer's transaction
|
|
INSERT INTO p2p_balance_transactions (
|
|
user_id, token, transaction_type, amount,
|
|
balance_before, balance_after, reference_type, reference_id,
|
|
description
|
|
) VALUES (
|
|
p_to_user_id, p_token, 'trade_receive', p_amount,
|
|
v_to_balance_before, v_to_balance_before + p_amount, p_reference_type, p_reference_id,
|
|
'Received from P2P trade'
|
|
);
|
|
|
|
RETURN json_build_object(
|
|
'success', true,
|
|
'transferred_amount', p_amount,
|
|
'from_user_id', p_from_user_id,
|
|
'to_user_id', p_to_user_id
|
|
);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- =====================================================
|
|
-- 6. REFUND ESCROW FUNCTION (Trade Cancellation)
|
|
-- =====================================================
|
|
|
|
CREATE OR REPLACE FUNCTION refund_escrow_internal(
|
|
p_user_id UUID,
|
|
p_token TEXT,
|
|
p_amount DECIMAL(20, 12),
|
|
p_reference_type TEXT DEFAULT 'trade',
|
|
p_reference_id UUID DEFAULT NULL
|
|
) RETURNS JSON AS $$
|
|
DECLARE
|
|
v_balance RECORD;
|
|
v_locked_before DECIMAL(20, 12);
|
|
BEGIN
|
|
-- Lock user's balance row
|
|
SELECT * INTO v_balance
|
|
FROM user_internal_balances
|
|
WHERE user_id = p_user_id AND token = p_token
|
|
FOR UPDATE;
|
|
|
|
-- Check sufficient locked balance
|
|
IF v_balance IS NULL OR v_balance.locked_balance < p_amount THEN
|
|
RETURN json_build_object(
|
|
'success', false,
|
|
'error', 'Insufficient locked balance for refund'
|
|
);
|
|
END IF;
|
|
|
|
v_locked_before := v_balance.locked_balance;
|
|
|
|
-- Move from locked back to available
|
|
UPDATE user_internal_balances
|
|
SET
|
|
locked_balance = locked_balance - p_amount,
|
|
available_balance = available_balance + p_amount,
|
|
updated_at = NOW()
|
|
WHERE user_id = p_user_id AND token = p_token;
|
|
|
|
-- Log the transaction
|
|
INSERT INTO p2p_balance_transactions (
|
|
user_id, token, transaction_type, amount,
|
|
balance_before, balance_after, reference_type, reference_id,
|
|
description
|
|
) VALUES (
|
|
p_user_id, p_token, 'escrow_refund', p_amount,
|
|
v_locked_before, v_locked_before - p_amount, p_reference_type, p_reference_id,
|
|
'Escrow refunded (trade cancelled)'
|
|
);
|
|
|
|
RETURN json_build_object(
|
|
'success', true,
|
|
'refunded_amount', p_amount,
|
|
'available_balance', v_balance.available_balance + p_amount,
|
|
'locked_balance', v_locked_before - p_amount
|
|
);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- =====================================================
|
|
-- 7. PROCESS DEPOSIT FUNCTION
|
|
-- =====================================================
|
|
|
|
CREATE OR REPLACE FUNCTION process_deposit(
|
|
p_user_id UUID,
|
|
p_token TEXT,
|
|
p_amount DECIMAL(20, 12),
|
|
p_tx_hash TEXT,
|
|
p_request_id UUID DEFAULT NULL
|
|
) RETURNS JSON AS $$
|
|
DECLARE
|
|
v_balance_before DECIMAL(20, 12) := 0;
|
|
BEGIN
|
|
-- Get current balance
|
|
SELECT available_balance INTO v_balance_before
|
|
FROM user_internal_balances
|
|
WHERE user_id = p_user_id AND token = p_token;
|
|
|
|
IF v_balance_before IS NULL THEN
|
|
v_balance_before := 0;
|
|
END IF;
|
|
|
|
-- Upsert balance
|
|
INSERT INTO user_internal_balances (
|
|
user_id, token, available_balance, total_deposited, last_deposit_at
|
|
) VALUES (
|
|
p_user_id, p_token, p_amount, p_amount, NOW()
|
|
)
|
|
ON CONFLICT (user_id, token)
|
|
DO UPDATE SET
|
|
available_balance = user_internal_balances.available_balance + p_amount,
|
|
total_deposited = user_internal_balances.total_deposited + p_amount,
|
|
last_deposit_at = NOW(),
|
|
updated_at = NOW();
|
|
|
|
-- Log the transaction
|
|
INSERT INTO p2p_balance_transactions (
|
|
user_id, token, transaction_type, amount,
|
|
balance_before, balance_after, reference_type, reference_id,
|
|
description
|
|
) VALUES (
|
|
p_user_id, p_token, 'deposit', p_amount,
|
|
v_balance_before, v_balance_before + p_amount, 'deposit_request', p_request_id,
|
|
'Deposit from blockchain TX: ' || p_tx_hash
|
|
);
|
|
|
|
-- Update request status if provided
|
|
IF p_request_id IS NOT NULL THEN
|
|
UPDATE p2p_deposit_withdraw_requests
|
|
SET
|
|
status = 'completed',
|
|
blockchain_tx_hash = p_tx_hash,
|
|
processed_at = NOW(),
|
|
updated_at = NOW()
|
|
WHERE id = p_request_id;
|
|
END IF;
|
|
|
|
RETURN json_build_object(
|
|
'success', true,
|
|
'deposited_amount', p_amount,
|
|
'new_balance', v_balance_before + p_amount
|
|
);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- =====================================================
|
|
-- 8. REQUEST WITHDRAW FUNCTION
|
|
-- =====================================================
|
|
|
|
CREATE OR REPLACE FUNCTION request_withdraw(
|
|
p_user_id UUID,
|
|
p_token TEXT,
|
|
p_amount DECIMAL(20, 12),
|
|
p_wallet_address TEXT
|
|
) RETURNS JSON AS $$
|
|
DECLARE
|
|
v_balance RECORD;
|
|
v_request_id UUID;
|
|
BEGIN
|
|
-- Lock user's balance
|
|
SELECT * INTO v_balance
|
|
FROM user_internal_balances
|
|
WHERE user_id = p_user_id AND token = p_token
|
|
FOR UPDATE;
|
|
|
|
-- Check sufficient available balance
|
|
IF v_balance IS NULL OR v_balance.available_balance < p_amount THEN
|
|
RETURN json_build_object(
|
|
'success', false,
|
|
'error', 'Insufficient available balance. Available: ' || COALESCE(v_balance.available_balance, 0)
|
|
);
|
|
END IF;
|
|
|
|
-- Lock the amount (move to locked_balance)
|
|
UPDATE user_internal_balances
|
|
SET
|
|
available_balance = available_balance - p_amount,
|
|
locked_balance = locked_balance + p_amount,
|
|
updated_at = NOW()
|
|
WHERE user_id = p_user_id AND token = p_token;
|
|
|
|
-- Create withdrawal request
|
|
INSERT INTO p2p_deposit_withdraw_requests (
|
|
user_id, request_type, token, amount, wallet_address, status
|
|
) VALUES (
|
|
p_user_id, 'withdraw', p_token, p_amount, p_wallet_address, 'pending'
|
|
) RETURNING id INTO v_request_id;
|
|
|
|
-- Log the transaction
|
|
INSERT INTO p2p_balance_transactions (
|
|
user_id, token, transaction_type, amount,
|
|
balance_before, balance_after, reference_type, reference_id,
|
|
description
|
|
) VALUES (
|
|
p_user_id, p_token, 'withdraw', -p_amount,
|
|
v_balance.available_balance, v_balance.available_balance - p_amount, 'withdraw_request', v_request_id,
|
|
'Withdrawal request to ' || p_wallet_address
|
|
);
|
|
|
|
RETURN json_build_object(
|
|
'success', true,
|
|
'request_id', v_request_id,
|
|
'amount', p_amount,
|
|
'wallet_address', p_wallet_address,
|
|
'status', 'pending'
|
|
);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- =====================================================
|
|
-- 9. GET USER BALANCE FUNCTION
|
|
-- =====================================================
|
|
|
|
CREATE OR REPLACE FUNCTION get_user_internal_balance(p_user_id UUID)
|
|
RETURNS JSON AS $$
|
|
DECLARE
|
|
v_balances JSON;
|
|
BEGIN
|
|
SELECT json_agg(
|
|
json_build_object(
|
|
'token', token,
|
|
'available_balance', available_balance,
|
|
'locked_balance', locked_balance,
|
|
'total_balance', available_balance + locked_balance,
|
|
'total_deposited', total_deposited,
|
|
'total_withdrawn', total_withdrawn
|
|
)
|
|
) INTO v_balances
|
|
FROM user_internal_balances
|
|
WHERE user_id = p_user_id;
|
|
|
|
RETURN COALESCE(v_balances, '[]'::json);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- =====================================================
|
|
-- 10. HELPER: UPDATE UPDATED_AT TRIGGER
|
|
-- =====================================================
|
|
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Apply trigger to tables
|
|
DROP TRIGGER IF EXISTS update_user_internal_balances_updated_at ON user_internal_balances;
|
|
CREATE TRIGGER update_user_internal_balances_updated_at
|
|
BEFORE UPDATE ON user_internal_balances
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
DROP TRIGGER IF EXISTS update_deposit_withdraw_requests_updated_at ON p2p_deposit_withdraw_requests;
|
|
CREATE TRIGGER update_deposit_withdraw_requests_updated_at
|
|
BEFORE UPDATE ON p2p_deposit_withdraw_requests
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- =====================================================
|
|
-- COMMENTS
|
|
-- =====================================================
|
|
|
|
COMMENT ON TABLE user_internal_balances IS 'User internal balances for P2P trading (OKX-style internal ledger)';
|
|
COMMENT ON TABLE p2p_deposit_withdraw_requests IS 'Requests for deposits/withdrawals that require blockchain transactions';
|
|
COMMENT ON TABLE p2p_balance_transactions IS 'Audit log of all balance changes';
|
|
COMMENT ON FUNCTION lock_escrow_internal IS 'Lock user balance for P2P escrow (internal ledger)';
|
|
COMMENT ON FUNCTION release_escrow_internal IS 'Release escrow to buyer on trade completion';
|
|
COMMENT ON FUNCTION refund_escrow_internal IS 'Refund escrow to seller on trade cancellation';
|
|
COMMENT ON FUNCTION process_deposit IS 'Credit user balance after deposit verification';
|
|
COMMENT ON FUNCTION request_withdraw IS 'Create withdrawal request and lock balance';
|