diff --git a/package.json b/package.json index ec56f32..27a6af4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.175", + "version": "1.0.177", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/components/wallet/DepositUSDTModal.tsx b/src/components/wallet/DepositUSDTModal.tsx index 1216a4b..f1358ab 100644 --- a/src/components/wallet/DepositUSDTModal.tsx +++ b/src/components/wallet/DepositUSDTModal.tsx @@ -16,6 +16,7 @@ import { AlertTriangle, } from 'lucide-react'; import { useTelegram } from '@/hooks/useTelegram'; +import { useWallet } from '@/contexts/WalletContext'; import { supabase } from '@/lib/supabase'; type Network = 'ton' | 'polkadot' | 'trc20'; @@ -84,6 +85,7 @@ interface Props { export function DepositUSDTModal({ isOpen, onClose }: Props) { const { hapticImpact, showAlert } = useTelegram(); + const { address: localWalletAddress } = useWallet(); const [selectedNetwork, setSelectedNetwork] = useState('ton'); const [depositCode, setDepositCode] = useState(''); @@ -134,6 +136,13 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) { } else { if (data?.code) setDepositCode(data.code); if (data?.trc20Address) setDepositAddress(data.trc20Address); + + // If database doesn't have wallet but we have local wallet, sync it + if (!data?.walletAddress && localWalletAddress) { + supabase.functions.invoke('save-wallet-address', { + body: { initData, walletAddress: localWalletAddress }, + }); + } } } catch (err) { console.error('Error fetching deposit info:', err); @@ -144,7 +153,7 @@ export function DepositUSDTModal({ isOpen, onClose }: Props) { }; fetchDepositInfo(); - }, [isOpen]); + }, [isOpen, localWalletAddress]); // Fetch deposits history useEffect(() => { diff --git a/src/lib/wallet-storage.ts b/src/lib/wallet-storage.ts index 4ea351a..abdc8ed 100644 --- a/src/lib/wallet-storage.ts +++ b/src/lib/wallet-storage.ts @@ -95,8 +95,9 @@ export async function syncWalletToSupabase( // eslint-disable-next-line @typescript-eslint/no-explicit-any const client = supabase as any; - // UPDATE existing user's wallet_address (don't create new user) - const { error } = await client + // UPDATE existing user's wallet_address in both tables + // Update 'users' table + await client .from('users') .update({ wallet_address: address, @@ -104,6 +105,15 @@ export async function syncWalletToSupabase( }) .eq('telegram_id', telegramId); + // Also update 'tg_users' table (used by deposit system) + const { error } = await client + .from('tg_users') + .update({ + wallet_address: address, + updated_at: new Date().toISOString(), + }) + .eq('telegram_id', telegramId); + if (error) { console.error('Wallet sync error:', error); throw new Error('Wallet adresa DB-ê re senkronîze nebû'); diff --git a/src/version.json b/src/version.json index e4b650d..b664a5d 100644 --- a/src/version.json +++ b/src/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.175", - "buildTime": "2026-02-08T00:56:36.820Z", - "buildNumber": 1770512196820 + "version": "1.0.177", + "buildTime": "2026-02-08T01:24:06.896Z", + "buildNumber": 1770513846897 } diff --git a/supabase/functions/check-deposits/index.ts b/supabase/functions/check-deposits/index.ts index 0d6af2a..ae61f71 100644 --- a/supabase/functions/check-deposits/index.ts +++ b/supabase/functions/check-deposits/index.ts @@ -22,8 +22,8 @@ const TON_API = 'https://tonapi.io/v2'; const TRON_API = 'https://api.trongrid.io'; const SUBSCAN_API = 'https://assethub-polkadot.api.subscan.io'; -// Contract addresses -const TON_USDT_MASTER = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'; // TON USDT Jetton +// Contract addresses - raw format from TonAPI +const TON_USDT_MASTER = '0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe'; // TON USDT Jetton const TRON_USDT_CONTRACT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'; // TRC20 USDT const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; diff --git a/supabase/functions/get-deposit-info/index.ts b/supabase/functions/get-deposit-info/index.ts index 392208c..a99f7d4 100644 --- a/supabase/functions/get-deposit-info/index.ts +++ b/supabase/functions/get-deposit-info/index.ts @@ -190,7 +190,7 @@ serve(async (req) => { const { data: existingUser } = await supabase .from('tg_users') - .select('id, deposit_index') + .select('id, deposit_index, wallet_address') .eq('telegram_id', telegramUser.id) .single(); @@ -277,6 +277,7 @@ serve(async (req) => { code: depositCode, trc20Address, depositIndex, + walletAddress: existingUser?.wallet_address || null, }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); diff --git a/supabase/functions/save-wallet-address/index.ts b/supabase/functions/save-wallet-address/index.ts new file mode 100644 index 0000000..4ba64bc --- /dev/null +++ b/supabase/functions/save-wallet-address/index.ts @@ -0,0 +1,147 @@ +/** + * 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_ORIGIN = 'https://telegram.pezkuwichain.io'; + +function getCorsHeaders(): Record { + return { + 'Access-Control-Allow-Origin': ALLOWED_ORIGIN, + '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 corsHeaders = getCorsHeaders(); + + 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 botToken = Deno.env.get('TELEGRAM_BOT_TOKEN'); + + if (!botToken) { + return new Response(JSON.stringify({ error: 'Server configuration error' }), { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + const telegramUser = validateInitData(initData, botToken); + 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' }, + }); + } +});