From e0fda83ddaab23f6492e2bd51e9bb09ec5a23ea5 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 8 Nov 2025 09:53:57 +0000 Subject: [PATCH] Create Web3Context.tsx - Handle Ethereum wallet authentication cgen-5298fa7511b84700a8069a80c6d5ec44 --- client/contexts/Web3Context.tsx | 175 ++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 client/contexts/Web3Context.tsx diff --git a/client/contexts/Web3Context.tsx b/client/contexts/Web3Context.tsx new file mode 100644 index 00000000..234de556 --- /dev/null +++ b/client/contexts/Web3Context.tsx @@ -0,0 +1,175 @@ +import React, { createContext, useContext, useState, useCallback, useEffect } from "react"; +import { aethexToast } from "@/lib/aethex-toast"; + +export interface Web3ContextType { + account: string | null; + isConnected: boolean; + isConnecting: boolean; + chainId: number | null; + connectWallet: () => Promise; + disconnectWallet: () => Promise; + signMessage: (message: string) => Promise; +} + +const Web3Context = createContext(undefined); + +export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [account, setAccount] = useState(null); + const [isConnected, setIsConnected] = useState(false); + const [isConnecting, setIsConnecting] = useState(false); + const [chainId, setChainId] = useState(null); + + // Check if wallet is already connected + useEffect(() => { + const checkConnection = async () => { + if (typeof window !== "undefined" && (window as any).ethereum) { + try { + const accounts = await (window as any).ethereum.request({ + method: "eth_accounts", + }); + if (accounts.length > 0) { + setAccount(accounts[0]); + setIsConnected(true); + const chainIdHex = await (window as any).ethereum.request({ + method: "eth_chainId", + }); + setChainId(parseInt(chainIdHex, 16)); + } + } catch (error) { + console.warn("Failed to check wallet connection:", error); + } + } + }; + + checkConnection(); + }, []); + + const connectWallet = useCallback(async () => { + if (typeof window === "undefined" || !(window as any).ethereum) { + aethexToast.error({ + title: "Metamask not installed", + description: "Please install Metamask to connect your wallet", + }); + throw new Error("Ethereum provider not found"); + } + + setIsConnecting(true); + try { + const accounts = await (window as any).ethereum.request({ + method: "eth_requestAccounts", + }); + + if (accounts.length === 0) { + throw new Error("No account selected"); + } + + setAccount(accounts[0]); + setIsConnected(true); + + // Get chain ID + const chainIdHex = await (window as any).ethereum.request({ + method: "eth_chainId", + }); + setChainId(parseInt(chainIdHex, 16)); + + aethexToast.success({ + title: "Wallet connected", + description: `Connected to ${accounts[0].slice(0, 6)}...${accounts[0].slice(-4)}`, + }); + } catch (error: any) { + console.error("Wallet connection error:", error); + aethexToast.error({ + title: "Connection failed", + description: error?.message || "Failed to connect wallet", + }); + throw error; + } finally { + setIsConnecting(false); + } + }, []); + + const disconnectWallet = useCallback(async () => { + setAccount(null); + setIsConnected(false); + setChainId(null); + aethexToast.info({ + title: "Wallet disconnected", + description: "You have been disconnected from your wallet", + }); + }, []); + + const signMessage = useCallback( + async (message: string): Promise => { + if (!account || typeof window === "undefined" || !(window as any).ethereum) { + throw new Error("Wallet not connected"); + } + + try { + const signature = await (window as any).ethereum.request({ + method: "personal_sign", + params: [message, account], + }); + return signature; + } catch (error: any) { + console.error("Message signing error:", error); + aethexToast.error({ + title: "Signing failed", + description: error?.message || "Failed to sign message", + }); + throw error; + } + }, + [account], + ); + + // Listen for account changes + useEffect(() => { + if (typeof window === "undefined" || !(window as any).ethereum) return; + + const handleAccountsChanged = (accounts: string[]) => { + if (accounts.length === 0) { + setAccount(null); + setIsConnected(false); + } else { + setAccount(accounts[0]); + setIsConnected(true); + } + }; + + const handleChainChanged = (chainIdHex: string) => { + setChainId(parseInt(chainIdHex, 16)); + }; + + (window as any).ethereum.on("accountsChanged", handleAccountsChanged); + (window as any).ethereum.on("chainChanged", handleChainChanged); + + return () => { + (window as any).ethereum?.removeListener("accountsChanged", handleAccountsChanged); + (window as any).ethereum?.removeListener("chainChanged", handleChainChanged); + }; + }, []); + + return ( + + {children} + + ); +}; + +export const useWeb3 = () => { + const context = useContext(Web3Context); + if (context === undefined) { + throw new Error("useWeb3 must be used within a Web3Provider"); + } + return context; +};