diff --git a/api/web3/verify.ts b/api/web3/verify.ts new file mode 100644 index 00000000..ae4f17a4 --- /dev/null +++ b/api/web3/verify.ts @@ -0,0 +1,130 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; +import { createClient } from "@supabase/supabase-js"; +import { verifyMessage } from "ethers"; + +const supabase = createClient( + process.env.SUPABASE_URL || "", + process.env.SUPABASE_SERVICE_ROLE || "", +); + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method !== "POST") { + res.setHeader("Allow", "POST"); + return res.status(405).json({ error: "Method not allowed" }); + } + + try { + const { wallet_address, signature, message, nonce } = req.body; + + if (!wallet_address || !signature || !message || !nonce) { + return res.status(400).json({ error: "Missing required fields" }); + } + + const normalizedAddress = wallet_address.toLowerCase(); + + // Verify the signature + let recoveredAddress: string; + try { + recoveredAddress = verifyMessage(message, signature).toLowerCase(); + } catch (error) { + console.error("Signature verification failed:", error); + return res.status(401).json({ error: "Invalid signature" }); + } + + // Check if recovered address matches provided address + if (recoveredAddress !== normalizedAddress) { + return res.status(401).json({ error: "Signature does not match wallet" }); + } + + // Verify nonce exists and is not expired + const { data: nonceData, error: nonceError } = await supabase + .from("web3_nonces") + .select("*") + .eq("wallet_address", normalizedAddress) + .eq("nonce", nonce) + .single(); + + if (nonceError || !nonceData) { + return res.status(401).json({ error: "Invalid or expired nonce" }); + } + + const expiresAt = new Date(nonceData.expires_at); + if (expiresAt < new Date()) { + return res.status(401).json({ error: "Nonce has expired" }); + } + + // Mark nonce as used + await supabase + .from("web3_nonces") + .update({ used_at: new Date().toISOString() }) + .eq("nonce", nonce); + + // Check if user already exists with this wallet + const { data: existingUser } = await supabase + .from("user_profiles") + .select("id, auth_id") + .eq("wallet_address", normalizedAddress) + .single(); + + if (existingUser) { + // User exists, link to their account + return res.status(200).json({ + success: true, + existing_user: true, + user_id: existingUser.id, + }); + } + + // Create new Web3 user + const email = `${normalizedAddress.substring(2)}@web3.aethex.dev`; + const username = normalizedAddress.substring(2, 10); + + // Create Supabase auth user + const { data: authData, error: authError } = await supabase.auth.admin.createUser({ + email, + password: require("crypto").randomBytes(32).toString("hex"), + email_confirm: true, + user_metadata: { + wallet_address: normalizedAddress, + auth_method: "web3", + }, + }); + + if (authError || !authData.user) { + console.error("Failed to create auth user:", authError); + return res.status(500).json({ error: "Failed to create account" }); + } + + // Create user profile + const { data: profileData, error: profileError } = await supabase + .from("user_profiles") + .insert({ + auth_id: authData.user.id, + email, + username, + wallet_address: normalizedAddress, + user_type: "game_developer", + full_name: `Web3 User ${username}`, + }) + .select() + .single(); + + if (profileError) { + console.error("Failed to create user profile:", profileError); + return res.status(500).json({ error: "Failed to create user profile" }); + } + + return res.status(200).json({ + success: true, + user_id: profileData.id, + email, + username, + wallet_address: normalizedAddress, + }); + } catch (error: any) { + console.error("Web3 verification error:", error); + return res.status(500).json({ + error: error?.message || "Verification failed", + }); + } +}