aethex-forge/client/contexts/Web3Context.tsx
2025-11-08 11:03:25 +00:00

193 lines
5.1 KiB
TypeScript

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<void>;
disconnectWallet: () => Promise<void>;
signMessage: (message: string) => Promise<string>;
}
const Web3Context = createContext<Web3ContextType | undefined>(undefined);
export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [account, setAccount] = useState<string | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [isConnecting, setIsConnecting] = useState(false);
const [chainId, setChainId] = useState<number | null>(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<string> => {
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 (
<Web3Context.Provider
value={{
account,
isConnected,
isConnecting,
chainId,
connectWallet,
disconnectWallet,
signMessage,
}}
>
{children}
</Web3Context.Provider>
);
};
export const useWeb3 = () => {
const context = useContext(Web3Context);
if (context === undefined) {
throw new Error("useWeb3 must be used within a Web3Provider");
}
return context;
};