feat: replace supabase auth with citizen/visa identity system for P2P

Replace all supabase.auth.getUser() calls with P2PIdentityContext that
resolves identity from on-chain citizen NFT or off-chain visa system.

- Add identityToUUID() in shared/lib/identity.ts (UUID v5 from citizen/visa number)
- Add P2PIdentityContext with citizen NFT detection and visa fallback
- Add p2p_visa migration for off-chain visa issuance
- Refactor p2p-fiat.ts: all functions now accept userId parameter
- Fix all P2P components to use useP2PIdentity() instead of useAuth()
- Update verify-deposit edge function: walletToUUID -> identityToUUID
- Add P2PLayout with identity gate (wallet/citizen/visa checks)
- Wrap all P2P routes with P2PLayout in App.tsx
This commit is contained in:
2026-02-23 19:54:57 +03:00
parent 350b65dec3
commit bb772668ba
25 changed files with 594 additions and 237 deletions
@@ -35,10 +35,10 @@ const RPC_WS = 'wss://rpc.pezkuwichain.io'
// Token decimals
const DECIMALS = 12
// Generate deterministic UUID v5 from wallet address
async function walletToUUID(walletAddress: string): Promise<string> {
// Generate deterministic UUID v5 from identity ID (citizen number or visa number)
async function identityToUUID(identityId: string): Promise<string> {
const NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'
const data = new TextEncoder().encode(walletAddress)
const data = new TextEncoder().encode(identityId)
const namespaceBytes = new Uint8Array(16)
const hex = NAMESPACE.replace(/-/g, '')
for (let i = 0; i < 16; i++) {
@@ -66,6 +66,7 @@ interface DepositRequest {
token: 'HEZ' | 'PEZ'
expectedAmount: number
walletAddress: string
identityId: string
blockNumber?: number
}
@@ -371,11 +372,11 @@ serve(async (req) => {
const serviceClient = createClient(supabaseUrl, supabaseServiceKey)
const body: DepositRequest = await req.json()
const { txHash, token, expectedAmount, walletAddress, blockNumber } = body
const { txHash, token, expectedAmount, walletAddress, identityId, blockNumber } = body
if (!txHash || !token || !expectedAmount || !walletAddress) {
if (!txHash || !token || !expectedAmount || !walletAddress || !identityId) {
return new Response(
JSON.stringify({ success: false, error: 'Missing required fields: txHash, token, expectedAmount, walletAddress' }),
JSON.stringify({ success: false, error: 'Missing required fields: txHash, token, expectedAmount, walletAddress, identityId' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
}
@@ -414,8 +415,8 @@ serve(async (req) => {
}
}
// Map wallet address to deterministic UUID
const userId = await walletToUUID(walletAddress)
// Map identity (citizen/visa number) to deterministic UUID
const userId = await identityToUUID(identityId)
// Create or update deposit request
const { data: depositRequest, error: requestError } = await serviceClient
@@ -0,0 +1,85 @@
-- P2P Visa System
-- Provides identity for non-citizen P2P traders
-- Citizens use their on-chain Citizen Number (from NFT)
-- Non-citizens apply for a Visa (off-chain, stored in Supabase)
CREATE TABLE IF NOT EXISTS public.p2p_visa (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
visa_number TEXT UNIQUE NOT NULL,
wallet_address TEXT UNIQUE NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
trust_level INTEGER NOT NULL DEFAULT 1,
issued_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ DEFAULT (now() + interval '1 year'),
metadata JSONB DEFAULT '{}'
);
CREATE INDEX IF NOT EXISTS idx_visa_wallet ON public.p2p_visa(wallet_address);
CREATE INDEX IF NOT EXISTS idx_visa_status ON public.p2p_visa(status);
-- Generate unique visa number: V-XXXXXX (6 digits)
CREATE OR REPLACE FUNCTION generate_visa_number()
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
num TEXT;
BEGIN
LOOP
num := 'V-' || lpad(floor(random() * 1000000)::text, 6, '0');
EXIT WHEN NOT EXISTS (SELECT 1 FROM public.p2p_visa WHERE visa_number = num);
END LOOP;
RETURN num;
END;
$$;
-- Issue a visa for a wallet address (returns the visa record)
CREATE OR REPLACE FUNCTION issue_p2p_visa(p_wallet_address TEXT)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_visa_number TEXT;
v_result JSONB;
BEGIN
-- Check if wallet already has a visa
IF EXISTS (SELECT 1 FROM public.p2p_visa WHERE wallet_address = p_wallet_address AND status = 'active') THEN
SELECT jsonb_build_object(
'success', true,
'visa_number', visa_number,
'already_exists', true
) INTO v_result
FROM public.p2p_visa
WHERE wallet_address = p_wallet_address AND status = 'active';
RETURN v_result;
END IF;
-- Generate unique visa number
v_visa_number := generate_visa_number();
-- Insert new visa
INSERT INTO public.p2p_visa (visa_number, wallet_address)
VALUES (v_visa_number, p_wallet_address);
RETURN jsonb_build_object(
'success', true,
'visa_number', v_visa_number,
'already_exists', false
);
END;
$$;
-- RLS: service role only (P2P operations go through edge functions)
ALTER TABLE public.p2p_visa ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Service role full access on p2p_visa"
ON public.p2p_visa
FOR ALL
USING (auth.role() = 'service_role');
-- Allow anon/authenticated to read their own visa by wallet address
CREATE POLICY "Users can read own visa"
ON public.p2p_visa
FOR SELECT
USING (true);