/** * Save Wallet Address - Saves user's Asset Hub wallet address */ import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { createHmac } from 'https://deno.land/std@0.177.0/node/crypto.ts'; const ALLOWED_ORIGINS = ['https://telegram.pezkuwichain.io', 'https://telegram.pezkiwi.app']; function getCorsHeaders(origin?: string | null): Record { const allowedOrigin = origin && ALLOWED_ORIGINS.some((o) => origin.startsWith(o)) ? origin : ALLOWED_ORIGINS[0]; return { 'Access-Control-Allow-Origin': allowedOrigin, 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-supabase-client-platform', 'Access-Control-Allow-Methods': 'POST, OPTIONS', }; } interface TelegramUser { id: number; first_name: string; } function validateInitData(initData: string, botToken: string): TelegramUser | null { try { const params = new URLSearchParams(initData); const hash = params.get('hash'); if (!hash) return null; params.delete('hash'); const sortedParams = Array.from(params.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${key}=${value}`) .join('\n'); const secretKey = createHmac('sha256', 'WebAppData').update(botToken).digest(); const calculatedHash = createHmac('sha256', secretKey).update(sortedParams).digest('hex'); if (calculatedHash !== hash) return null; const authDate = parseInt(params.get('auth_date') || '0'); const now = Math.floor(Date.now() / 1000); if (now - authDate > 86400) return null; const userStr = params.get('user'); if (!userStr) return null; return JSON.parse(userStr) as TelegramUser; } catch { return null; } } // Validate Substrate address format (SS58) function isValidSubstrateAddress(address: string): boolean { // Basic validation: starts with 1 or 5, length 47-48, alphanumeric if (!address) return false; if (address.length < 46 || address.length > 48) return false; if (!address.match(/^[1-9A-HJ-NP-Za-km-z]+$/)) return false; // Most Polkadot/Kusama addresses start with 1 or 5 if (!['1', '5'].includes(address[0])) return false; return true; } serve(async (req) => { const origin = req.headers.get('origin'); const corsHeaders = getCorsHeaders(origin); if (req.method === 'OPTIONS') { return new Response('ok', { headers: corsHeaders }); } try { const body = await req.json(); const { initData, walletAddress } = body; if (!initData) { return new Response(JSON.stringify({ error: 'Missing initData' }), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } if (!walletAddress) { return new Response(JSON.stringify({ error: 'Missing walletAddress' }), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } // Validate wallet address format if (!isValidSubstrateAddress(walletAddress)) { return new Response(JSON.stringify({ error: 'Invalid wallet address format' }), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const botTokens: string[] = []; const _mainToken = Deno.env.get('TELEGRAM_BOT_TOKEN'); const _krdToken = Deno.env.get('TELEGRAM_BOT_TOKEN_KRD'); if (_mainToken) botTokens.push(_mainToken); if (_krdToken) botTokens.push(_krdToken); if (botTokens.length === 0) { return new Response(JSON.stringify({ error: 'Server configuration error' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } let telegramUser: TelegramUser | null = null; for (const bt of botTokens) { telegramUser = validateInitData(initData, bt); if (telegramUser) break; } if (!telegramUser) { return new Response(JSON.stringify({ error: 'Invalid Telegram data' }), { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } const supabase = createClient(supabaseUrl, supabaseServiceKey, { auth: { autoRefreshToken: false, persistSession: false }, }); // Update user's wallet address const { error: updateError } = await supabase .from('tg_users') .update({ wallet_address: walletAddress }) .eq('telegram_id', telegramUser.id); if (updateError) { console.error('Error updating wallet address:', updateError); return new Response(JSON.stringify({ error: 'Failed to save wallet address' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } return new Response(JSON.stringify({ success: true }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } catch (err) { console.error('Save wallet address error:', err); return new Response(JSON.stringify({ error: 'Internal server error' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } });