Last active
September 16, 2021 14:42
-
-
Save agcty/d06a83c422cd0612c992452a6e4824e2 to your computer and use it in GitHub Desktop.
useMetamask hook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { createContext, useEffect, useReducer, useRef } from "react" | |
import MetaMaskOnboarding from "@metamask/onboarding" | |
import Cookies from "js-cookie" | |
type ApiState = | |
| "IDLE" | |
| "CONNECT_INIT" | |
| "CONNECTING" | |
| "ERROR" | |
| "READY" | |
| "SWITCHING_CHAINS" | |
| "ONBOARDING" | |
interface State { | |
accounts: string[] | |
status: ApiState | |
error: Error | |
} | |
type ContextValue = State & { | |
connect: () => Promise<void> | |
switchChains: () => Promise<void> | |
} | |
const INIT_STATE: ContextValue = { | |
accounts: [], | |
status: "ONBOARDING", | |
error: null, | |
connect: null, | |
switchChains: null, | |
} | |
const Web3Context = createContext<ContextValue>(INIT_STATE) | |
type Action = | |
| { type: "IDLE" } | |
| { type: "CONNECT"; payload: string[] } | |
| { type: "ONBOARDING" } | |
| { type: "CONNECT_SUCCESS" } | |
| { type: "CONNECT_ERROR"; payload: Error } | |
| { type: "INCORRECT_NETWORK"; payload: Error } | |
| { type: "SWITCHING_CHAINS" } | |
const reducer = (state: State, action: Action): State => { | |
switch (action.type) { | |
case "IDLE": | |
return { ...state, status: "IDLE" } | |
case "CONNECT": | |
return { ...state, status: "CONNECTING", accounts: action.payload } | |
case "CONNECT_SUCCESS": | |
return { ...state, status: "READY" } | |
case "CONNECT_ERROR": | |
return { ...state, status: "ERROR", error: action.payload } | |
case "SWITCHING_CHAINS": | |
return { ...state, status: "SWITCHING_CHAINS" } | |
default: | |
throw new Error(`Unknown type!`) | |
} | |
} | |
function MetamaskContextProvider({ children }: { children: React.ReactNode }) { | |
const [state, dispatch] = useReducer(reducer, INIT_STATE) | |
const onboarding = useRef<MetaMaskOnboarding>() | |
useEffect(() => { | |
if (!onboarding.current) { | |
onboarding.current = new MetaMaskOnboarding() | |
} | |
}, []) | |
useEffect(() => { | |
if (MetaMaskOnboarding.isMetaMaskInstalled()) { | |
//based on https://docs.metamask.io/guide/rpc-api.html#other-rpc-methods | |
if (state.accounts.length > 0) { | |
dispatch({ type: "CONNECT_SUCCESS" }) | |
onboarding.current.stopOnboarding() | |
} else { | |
dispatch({ type: "IDLE" }) | |
} | |
} | |
}, [state.accounts]) | |
useEffect(() => { | |
const run = async () => { | |
function handleNewAccounts(newAccounts: string[]) { | |
dispatch({ type: "CONNECT", payload: newAccounts }) | |
} | |
const consentGiven = Cookies.get("consent") | |
// metmask is installed and window.ethereum is available | |
if (MetaMaskOnboarding.isMetaMaskInstalled()) { | |
// set listener before checking for consent so it's actually set | |
window["ethereum"].on("accountsChanged", handleNewAccounts) | |
window["ethereum"].on("chainChanged", (chainId: string) => { | |
if (chainId !== "0x13881") { | |
dispatch({ | |
type: "CONNECT_ERROR", | |
payload: new Error("Incorrect Network"), | |
}) | |
} else { | |
dispatch({ type: "CONNECT_SUCCESS" }) | |
} | |
}) | |
// check if consent is given to request accounts, this is to prevent requesting when opening the website | |
if (consentGiven !== "true") { | |
return | |
} else { | |
await switchChains() | |
window["ethereum"] | |
.request({ method: "eth_requestAccounts" }) | |
.then(handleNewAccounts) | |
} | |
return () => { | |
window["ethereum"]?.off?.("accountsChanged", handleNewAccounts) | |
} | |
} | |
} | |
run() | |
}, []) | |
async function switchChains() { | |
try { | |
await window["ethereum"].request({ | |
method: "wallet_switchEthereumChain", | |
params: [{ chainId: "0x13881" }], | |
}) | |
} catch (error) { | |
if (error.code === 4902 || error.code === -32603) { | |
try { | |
await window["ethereum"].request({ | |
method: "wallet_addEthereumChain", | |
params: [ | |
{ | |
chainId: "0x13881", | |
chainName: "Mumbai", | |
rpcUrls: ["https://rpc-mumbai.matic.today"], | |
nativeCurrency: { | |
name: "MATIC", | |
symbol: "MATIC", | |
decimals: 18, | |
}, | |
blockExplorerUrls: ["https://mumbai.polygonscan.com"], | |
}, | |
], | |
}) | |
await switchChains() | |
} catch (addError) { | |
console.log("aborted") | |
} | |
} | |
} | |
} | |
async function connect() { | |
if (MetaMaskOnboarding.isMetaMaskInstalled()) { | |
await switchChains() | |
let newAccounts = await window["ethereum"].request({ | |
method: "eth_requestAccounts", | |
}) | |
dispatch({ type: "CONNECT", payload: newAccounts }) | |
Cookies.set("consent", true) | |
} else { | |
onboarding.current.startOnboarding() | |
} | |
} | |
return ( | |
<Web3Context.Provider value={{ ...state, connect, switchChains }}> | |
{children} | |
</Web3Context.Provider> | |
) | |
} | |
function useMetamask() { | |
return React.useContext(Web3Context) | |
} | |
export { MetamaskContextProvider, useMetamask } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
E.g to use in a React app, supply the context at the top of your project (in next.js it's _app):
and in a component: