Create Web3Context.tsx - Handle Ethereum wallet authentication
cgen-5298fa7511b84700a8069a80c6d5ec44
This commit is contained in:
parent
5f07d43aed
commit
e0fda83dda
1 changed files with 175 additions and 0 deletions
175
client/contexts/Web3Context.tsx
Normal file
175
client/contexts/Web3Context.tsx
Normal file
|
|
@ -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<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;
|
||||
};
|
||||
Loading…
Reference in a new issue