aethex-forge/api/user/link-web3.ts
2025-11-08 11:03:25 +00:00

101 lines
3.1 KiB
TypeScript

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 authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing authorization header" });
}
const token = authHeader.substring(7);
// Verify token with Supabase
const { data: userData, error: authError } =
await supabase.auth.getUser(token);
if (authError || !userData.user) {
return res.status(401).json({ error: "Invalid token" });
}
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) {
return res.status(401).json({ error: "Invalid signature" });
}
if (recoveredAddress !== normalizedAddress) {
return res.status(401).json({ error: "Signature does not match wallet" });
}
// Verify nonce
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" });
}
// Mark nonce as used
await supabase
.from("web3_nonces")
.update({ used_at: new Date().toISOString() })
.eq("nonce", nonce);
// Link wallet to existing user
const { error: linkError } = await supabase.from("web3_wallets").insert({
user_id: userData.user.id,
wallet_address: normalizedAddress,
chain_id: 1, // Ethereum mainnet
});
if (
linkError &&
!linkError.message.includes("violates unique constraint")
) {
console.error("Failed to link wallet:", linkError);
return res.status(500).json({ error: "Failed to link wallet" });
}
// Also update user_profiles wallet_address column if available
await supabase
.from("user_profiles")
.update({ wallet_address: normalizedAddress })
.eq("auth_id", userData.user.id);
return res.status(200).json({
success: true,
message: "Web3 wallet linked successfully",
wallet_address: normalizedAddress,
});
} catch (error: any) {
console.error("Link Web3 error:", error);
return res.status(500).json({
error: error?.message || "Failed to link wallet",
});
}
}