diff --git a/client/components/settings/WalletVerification.tsx b/client/components/settings/WalletVerification.tsx new file mode 100644 index 00000000..b4a777f1 --- /dev/null +++ b/client/components/settings/WalletVerification.tsx @@ -0,0 +1,280 @@ +import { useState, useEffect } from "react"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { CheckCircle, AlertCircle, Copy, Unlink } from "lucide-react"; +import { useAuth } from "@/contexts/AuthContext"; +import { aethexToast } from "@/lib/aethex-toast"; + +const API_BASE = import.meta.env.VITE_API_BASE || ""; + +interface WalletVerificationProps { + onWalletUpdated?: (walletAddress: string | null) => void; +} + +const isValidEthereumAddress = (address: string): boolean => { + return /^0x[a-fA-F0-9]{40}$/.test(address); +}; + +const formatWalletAddress = (address: string): string => { + if (!isValidEthereumAddress(address)) return address; + return `${address.slice(0, 6)}...${address.slice(-4)}`; +}; + +export const WalletVerification = ({ + onWalletUpdated, +}: WalletVerificationProps) => { + const { user, profile } = useAuth(); + const [walletInput, setWalletInput] = useState(""); + const [connectedWallet, setConnectedWallet] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [isCopied, setIsCopied] = useState(false); + + useEffect(() => { + // Initialize with existing wallet address if available + if (profile?.wallet_address) { + setConnectedWallet(profile.wallet_address); + } + }, [profile?.wallet_address]); + + const handleConnect = async () => { + if (!walletInput.trim()) { + aethexToast.warning("Please enter a wallet address"); + return; + } + + const normalized = walletInput.trim().toLowerCase(); + if (!isValidEthereumAddress(normalized)) { + aethexToast.warning( + "Invalid Ethereum address. Must be 0x followed by 40 hexadecimal characters." + ); + return; + } + + if (!user?.id) { + aethexToast.error("User not authenticated"); + return; + } + + setIsLoading(true); + try { + const response = await fetch(`${API_BASE}/api/profile/wallet-verify`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + user_id: user.id, + wallet_address: normalized, + }), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.error || `HTTP ${response.status}: Failed to connect wallet` + ); + } + + const data = await response.json(); + setConnectedWallet(normalized); + setWalletInput(""); + aethexToast.success("✅ Wallet connected successfully!"); + + if (onWalletUpdated) { + onWalletUpdated(normalized); + } + } catch (error: any) { + console.error("[Wallet Verification] Error:", error?.message); + aethexToast.error( + error?.message || "Failed to connect wallet. Please try again." + ); + } finally { + setIsLoading(false); + } + }; + + const handleDisconnect = async () => { + if (!user?.id || !connectedWallet) return; + + setIsLoading(true); + try { + const response = await fetch(`${API_BASE}/api/profile/wallet-verify`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + user_id: user.id, + }), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.error || `HTTP ${response.status}: Failed to disconnect wallet` + ); + } + + setConnectedWallet(null); + aethexToast.success("Wallet disconnected"); + + if (onWalletUpdated) { + onWalletUpdated(null); + } + } catch (error: any) { + console.error("[Wallet Verification] Error:", error?.message); + aethexToast.error( + error?.message || "Failed to disconnect wallet. Please try again." + ); + } finally { + setIsLoading(false); + } + }; + + const handleCopyAddress = () => { + if (connectedWallet) { + navigator.clipboard.writeText(connectedWallet); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } + }; + + return ( + + + + 🔐 Wallet Verification + {connectedWallet && ( + + + Connected + + )} + + + Connect your Ethereum wallet to enable Web3 identity verification. + This wallet address will be used to verify ownership of your{" "} + .aethex TLD + once the Axiom Protocol is live. + + + + {connectedWallet ? ( +
+
+
+ Connected Wallet +
+
+ + {formatWalletAddress(connectedWallet)} + + +
+ {isCopied && ( +

+ ✓ Copied to clipboard +

+ )} +
+ +
+

+ What's this for? +

+
    +
  • + ✓ Proves you're the owner of this wallet (Web3 identity) +
  • +
  • + ✓ Will unlock your .aethex TLD + verification when the Protocol launches +
  • +
  • ✓ No smart contracts or gas fees required right now
  • +
  • ✓ Your wallet address is private and only visible to you
  • +
+
+ + +
+ ) : ( +
+
+
+ +
+

No wallet connected yet.

+

+ Enter your Ethereum wallet address below to verify your + identity. +

+
+
+
+ +
+
+ + setWalletInput(e.target.value)} + disabled={isLoading} + className="mt-2 font-mono text-sm" + /> +

+ Format: 0x followed by 40 hexadecimal characters +

+
+ + +
+ +
+

💡 How it works:

+
    +
  1. 1. Enter your wallet address
  2. +
  3. 2. We save it securely to your profile
  4. +
  5. + 3. When Phase 2 launches, we'll verify you own the .aethex TLD + registered to this wallet +
  6. +
+
+
+ )} +
+
+ ); +}; + +export default WalletVerification;