import { atom, useRecoilState } from "recoil"; import { ethers, providers } from "ethers"; import { assoc } from "ramda"; // ethereum isn't on TS standard Window type: export declare const window: Window & { ethereum: providers.ExternalProvider & { on: any; }; }; type Web3ProviderState = { chainId?: number; provider?: ethers.providers.Web3Provider; signer?: ethers.providers.JsonRpcSigner; account?: string; }; export const web3ProviderState = atom({ key: "web3ProviderState", dangerouslyAllowMutability: true, default: { chainId: undefined, provider: undefined, signer: undefined, account: undefined, }, }); type ChainInfo = { chainId: number; chainName: string; nativeCurrency: { name: string; symbol: string; decimals: number; }; rpcUrls: any; // ["https://bsc-dataseed.binance.org/"]; blockExplorerUrls: any; //["https://bscscan.com/"]; }; /** * A hook to provide ethers' 'connect' function anywhere, and give global access to the * returned values (user's account, provider, signer). * - As this uses recoil state, consumers can use an effect to change UI/perform other functions * when a user connects */ export const useWeb3Provider = () => { const [state, setState] = useRecoilState(web3ProviderState); const connect = async () => { const provider = new ethers.providers.Web3Provider(window.ethereum, "any"); await provider.send("eth_requestAccounts", []); const chainIdHex = await provider.send("eth_chainId", []); const chainId = parseInt(chainIdHex, 16); provider.on("network", (newNetwork, oldNetwork) => { setState(assoc("chainId", newNetwork.chainId)); // this is a bit weak - reloading the page if network changes oldNetwork && window.location.reload(); }); window.ethereum.on("accountsChanged", (accounts: string[]) => { setState((s) => ({ ...s, account: accounts[0], })); }); const signer = await provider.getSigner(); const account = await signer.getAddress(); setState((s) => ({ ...s, provider, signer, account, chainId })); return { account, provider, signer, }; }; //A function to switch metamask wallet chain using window.ethereum const switchChain = async (chainInfo: ChainInfo) => { if (window.ethereum != undefined) { try { //@ts-ignore await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: `0x${chainInfo.chainId.toString(16)}` }], }); } catch (switchError: any) { // This error code indicates that the chain has not been added to MetaMask. if (switchError.code === 4902) { try { //@ts-ignore await window.ethereum.request({ method: "wallet_addEthereumChain", params: [ { chainId: `0x${chainInfo.chainId.toString(16)}`, chainName: chainInfo.chainName, nativeCurrency: { name: chainInfo.nativeCurrency.name, symbol: chainInfo.nativeCurrency.symbol, decimals: chainInfo.nativeCurrency.decimals, }, rpcUrls: chainInfo.rpcUrls, blockExplorerUrls: chainInfo.blockExplorerUrls, }, ], }); } catch (addError) { console.error(addError); } } // handle other "switch" errors } } }; const checkConnectedAccounts = async (): Promise => { const accounts = await window.ethereum.request?.({ method: "eth_accounts", }); return accounts; }; const checkConnectedChain = async () => { const chainId = await window.ethereum.request?.({ method: "eth_chainId" }); setState(assoc("chainId", parseInt(chainId, 16))); return chainId; }; return { ...state, connect, checkConnectedAccounts, checkConnectedChain, switchChain, }; };