UNPKG

@web3auth/metamask-adapter

Version:
178 lines (159 loc) 6.38 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import detectEthereumProvider from "@metamask/detect-provider"; import { ADAPTER_CATEGORY, ADAPTER_CATEGORY_TYPE, ADAPTER_EVENTS, ADAPTER_NAMESPACES, ADAPTER_STATUS, ADAPTER_STATUS_TYPE, AdapterInitOptions, AdapterNamespaceType, BaseAdapterSettings, CHAIN_NAMESPACES, ChainNamespaceType, CONNECTED_EVENT_DATA, CustomChainConfig, IProvider, log, UserInfo, WALLET_ADAPTERS, WalletInitializationError, WalletLoginError, Web3AuthError, } from "@web3auth/base"; import { BaseEvmAdapter } from "@web3auth/base-evm-adapter"; interface EthereumProvider extends IProvider { isMetaMask?: boolean; isConnected: () => boolean; chainId: string; } export type MetamaskAdapterOptions = BaseAdapterSettings; class MetamaskAdapter extends BaseEvmAdapter<void> { readonly adapterNamespace: AdapterNamespaceType = ADAPTER_NAMESPACES.EIP155; readonly currentChainNamespace: ChainNamespaceType = CHAIN_NAMESPACES.EIP155; readonly type: ADAPTER_CATEGORY_TYPE = ADAPTER_CATEGORY.EXTERNAL; readonly name: string = WALLET_ADAPTERS.METAMASK; public status: ADAPTER_STATUS_TYPE = ADAPTER_STATUS.NOT_READY; private metamaskProvider: EthereumProvider | null = null; get provider(): IProvider | null { if (this.status !== ADAPTER_STATUS.NOT_READY && this.metamaskProvider) { return this.metamaskProvider; } return null; } set provider(_: IProvider | null) { throw new Error("Not implemented"); } async init(options: AdapterInitOptions = {}): Promise<void> { await super.init(options); super.checkInitializationRequirements(); this.metamaskProvider = (await detectEthereumProvider({ mustBeMetaMask: true, silent: true, timeout: 1000 })) as EthereumProvider; if (!this.metamaskProvider) throw WalletInitializationError.notInstalled("Metamask extension is not installed"); this.status = ADAPTER_STATUS.READY; this.emit(ADAPTER_EVENTS.READY, WALLET_ADAPTERS.METAMASK); try { log.debug("initializing metamask adapter"); if (options.autoConnect) { this.rehydrated = true; await this.connect(); } } catch (error) { this.emit(ADAPTER_EVENTS.ERRORED, error); } } async connect(): Promise<IProvider | null> { super.checkConnectionRequirements(); if (!this.metamaskProvider) throw WalletLoginError.notConnectedError("Not able to connect with metamask"); const { ethereum } = window as any; const isPhantom = Boolean("isPhantom" in ethereum); // check which is the active provider if (ethereum && ethereum.isMetaMask && isPhantom) { // this means phantom is the active provider. if (ethereum.providers && ethereum.providers.length > 0) { const provider = ethereum.providers.find((p: any) => p.isMetaMask && !p.overrideIsMetaMask); if (provider) { ethereum.setProvider(provider); } } } else if (ethereum && (ethereum.providers || []).length > 0) { // this means that there are another providers than metamask (like coinbase). const provider = ethereum.providers.find((p: any) => p.isMetaMask); if (provider) { ethereum.setSelectedProvider(provider); } } this.status = ADAPTER_STATUS.CONNECTING; this.emit(ADAPTER_EVENTS.CONNECTING, { adapter: WALLET_ADAPTERS.METAMASK }); try { await this.metamaskProvider.request({ method: "eth_requestAccounts" }); const { chainId } = this.metamaskProvider; if (chainId !== (this.chainConfig as CustomChainConfig).chainId) { await this.addChain(this.chainConfig as CustomChainConfig, true); await this.switchChain(this.chainConfig as CustomChainConfig, true); } this.status = ADAPTER_STATUS.CONNECTED; if (!this.provider) throw WalletLoginError.notConnectedError("Failed to connect with provider"); const disconnectHandler = () => { // ready to be connected again this.disconnect(); this.provider?.removeListener("disconnect", disconnectHandler); }; this.provider.on("disconnect", disconnectHandler); this.emit(ADAPTER_EVENTS.CONNECTED, { adapter: WALLET_ADAPTERS.METAMASK, reconnected: this.rehydrated } as CONNECTED_EVENT_DATA); return this.provider; } catch (error) { // ready again to be connected this.status = ADAPTER_STATUS.READY; this.rehydrated = false; this.emit(ADAPTER_EVENTS.ERRORED, error); if (error instanceof Web3AuthError) throw error; throw WalletLoginError.connectionError("Failed to login with metamask wallet"); } } async disconnect(options: { cleanup: boolean } = { cleanup: false }): Promise<void> { await super.disconnectSession(); this.provider?.removeAllListeners(); if (options.cleanup) { this.status = ADAPTER_STATUS.NOT_READY; this.metamaskProvider = null; } else { // ready to be connected again this.status = ADAPTER_STATUS.READY; } await super.disconnect(); } async getUserInfo(): Promise<Partial<UserInfo>> { if (this.status !== ADAPTER_STATUS.CONNECTED) throw WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); return {}; } public async addChain(chainConfig: CustomChainConfig, init = false): Promise<void> { super.checkAddChainRequirements(chainConfig, init); await this.metamaskProvider?.request({ method: "wallet_addEthereumChain", params: [ { chainId: chainConfig.chainId, chainName: chainConfig.displayName, rpcUrls: [chainConfig.rpcTarget], blockExplorerUrls: [chainConfig.blockExplorer], nativeCurrency: { name: chainConfig.tickerName, symbol: chainConfig.ticker, decimals: chainConfig.decimals || 18, }, }, ], }); this.addChainConfig(chainConfig); } public async switchChain(params: { chainId: string }, init = false): Promise<void> { super.checkSwitchChainRequirements(params, init); await this.metamaskProvider?.request({ method: "wallet_switchEthereumChain", params: [{ chainId: params.chainId }], }); this.setAdapterSettings({ chainConfig: this.getChainConfig(params.chainId) as CustomChainConfig }); } } export { MetamaskAdapter };