@vjgee this is the implementation, thanks!
This is the hook to provide all login methods: useWeb3Auth.tsx
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useRef, useState } from 'react';
import { Web3AuthNoModal } from '@web3auth/no-modal';
import { WALLET_ADAPTERS, CHAIN_NAMESPACES, SafeEventEmitterProvider } from '@web3auth/base';
import { OpenloginAdapter, OpenloginUserInfo } from '@web3auth/openlogin-adapter';
import { EthereumPrivateKeyProvider } from '@web3auth/ethereum-provider';
import { MetamaskAdapter } from '@web3auth/metamask-adapter';
import {
WalletConnectV2Adapter,
getWalletConnectV2Settings,
} from '@web3auth/wallet-connect-v2-adapter';
import { WalletConnectModal } from '@walletconnect/modal';
import {
openLoginAdapterSettings,
chainConfig,
SOCIAL_AUTH_PROVIDER_MAP,
WC_CHAIN_IDS,
} from '@constants/web3Auth';
import { clientId, wcProjectID, web3AuthNetwork } from '@constants/envVariables';
import { TorusWalletConnectorPlugin } from '@web3auth/torus-wallet-connector-plugin';
import { APIServiceAuthenticationParams } from '@api/type';
import { getAuthInfo, getWalletAddress } from '@utils/web3auth.utils';
import { torusWalletInitOptions } from '@configs/torusWallet';
import useScreenDetector from './useScreenDetector';
const walletConnectModal = new WalletConnectModal({ projectId: wcProjectID });
interface IUserInfo {
email?: string;
walletAddress: string;
loginProvider: string;
openloginUserInfo?: Partial<OpenloginUserInfo>;
}
export interface IWeb3AuthState {
web3auth: Web3AuthNoModal | null;
torusPlugin: TorusWalletConnectorPlugin | null;
provider: SafeEventEmitterProvider | null;
authenticateUser: () => Promise<any>;
logout: () => Promise<void>;
isConnecting: boolean;
loginWithGoogle: () => Promise<void>;
loginWithFacebook: () => Promise<void>;
loginWithTwitter: () => Promise<void>;
loginWithDiscord: () => Promise<void>;
loginWithWCv2: () => Promise<void>;
loginWithMM: () => Promise<void>;
error: string | null;
setError: (error: string | null) => void;
isLoading: boolean;
isUserInfoLoading: boolean;
socialLogin: (provider: string) => Promise<void>;
userInfo: IUserInfo | null;
authInfo: APIServiceAuthenticationParams;
}
export const useWeb3Auth = (): IWeb3AuthState => {
const { screenType, SCREEN_TYPE_MAP } = useScreenDetector();
const popUpClosedByUser = useRef<boolean>(false);
const [web3auth, setWeb3auth] = useState<Web3AuthNoModal | null>(null);
const [torusPlugin, setTorusPlugin] = useState<TorusWalletConnectorPlugin | null>(null);
const [provider, setProvider] = useState<SafeEventEmitterProvider | null>(null);
const [error, setError] = useState<string | null>(null);
const [isUserInfoLoading, setIsUserInfoLoading] = useState(false);
const [userInfo, setUserInfo] = useState<IUserInfo | null>(null);
const [isConnecting, setIsConnecting] = useState<boolean>(false);
const [authInfo, setAuthInfo] = useState<APIServiceAuthenticationParams>({});
const isLoading = useRef(false);
useEffect(() => {
const init = async () => {
try {
if (isLoading.current || screenType == SCREEN_TYPE_MAP.INIT) return;
isLoading.current = true;
const web3auth = new Web3AuthNoModal({
clientId,
chainConfig,
web3AuthNetwork,
});
const privateKeyProvider = new EthereumPrivateKeyProvider({
config: { chainConfig },
});
// Open login adapter (for social logins)
const openloginAdapter = new OpenloginAdapter({
adapterSettings: {
...openLoginAdapterSettings,
uxMode: screenType === SCREEN_TYPE_MAP.MOBILE ? 'redirect' : 'popup',
},
privateKeyProvider,
});
web3auth.configureAdapter(openloginAdapter);
// Wallet connect v2 adapter
const defaultWcSettings = await getWalletConnectV2Settings(
CHAIN_NAMESPACES.EIP155,
WC_CHAIN_IDS,
wcProjectID,
);
const walletConnectV2Adapter = new WalletConnectV2Adapter({
adapterSettings: {
qrcodeModal: walletConnectModal,
...defaultWcSettings.adapterSettings,
},
loginSettings: { ...defaultWcSettings.loginSettings },
web3AuthNetwork,
});
web3auth.configureAdapter(walletConnectV2Adapter);
// Metamask adapter
const metamaskAdapter = new MetamaskAdapter({});
web3auth.configureAdapter(metamaskAdapter);
web3auth.on('errored', (e) => {
if (
e?.message?.toLowerCase?.().includes?.('closed by the user') ||
e?.message?.toLowerCase?.().includes?.('user rejected the request')
) {
popUpClosedByUser.current = true;
setError(null);
}
console.error(e);
});
setWeb3auth(web3auth);
// Initialize TORUS EVM Wallet Plugin
const torusPlugin = new TorusWalletConnectorPlugin({
torusWalletOpts: {},
walletInitOptions: torusWalletInitOptions,
});
await web3auth.addPlugin(torusPlugin);
await web3auth.init();
if (web3auth.connected) {
setProvider(web3auth.provider);
} else {
setProvider(null);
}
setTorusPlugin(torusPlugin);
} catch (error: unknown) {
setError(error?.toString?.() || 'Internal Error');
console.error(error);
} finally {
isLoading.current = false;
}
};
init();
}, [screenType]);
// Get user info from web3auth
useEffect(() => {
if (!provider || !web3auth?.connected) {
return;
}
const fetchUserInfo = async () => {
setIsUserInfoLoading(true);
try {
const walletAddress = await getWalletAddress(provider);
const { email, appPubKey, authToken, loginProvider, userInfoRaw } = await getAuthInfo(
web3auth,
);
if (appPubKey && authToken) {
setAuthInfo({
appPubKey,
authToken,
});
}
setUserInfo({
email,
walletAddress,
loginProvider,
openloginUserInfo: userInfoRaw,
});
} finally {
setIsUserInfoLoading(false);
}
};
fetchUserInfo();
}, [provider, web3auth?.connected]);
const socialLogin = async (provider: string) => {
popUpClosedByUser.current = false;
if (!web3auth) {
uiConsole('web3auth not initialized yet');
return;
}
if (!provider) {
uiConsole('web3auth not initialized yet');
return;
}
setIsConnecting(true);
try {
const web3authProvider = await web3auth.connectTo(WALLET_ADAPTERS.OPENLOGIN, {
loginProvider: provider,
});
setProvider(web3authProvider);
setError('');
} catch (e: unknown) {
if ((e as any)?.message.includes('Wallet is not ready yet, Already connecting')) {
console.error('already connecting error: ', e);
} else if (screenType === SCREEN_TYPE_MAP.WEB_BROWSER && !popUpClosedByUser.current)
// NOTE: we use redirect instead of popup in mobile
setError(e?.toString() || 'Internal error');
console.error(e);
} finally {
setIsConnecting(false);
}
};
const loginWithGoogle = async () => {
socialLogin(SOCIAL_AUTH_PROVIDER_MAP.GOOGLE);
};
const loginWithFacebook = async () => {
socialLogin(SOCIAL_AUTH_PROVIDER_MAP.FACEBOOK);
};
const loginWithTwitter = async () => {
socialLogin(SOCIAL_AUTH_PROVIDER_MAP.TWITTER);
};
const loginWithDiscord = async () => {
socialLogin(SOCIAL_AUTH_PROVIDER_MAP.DISCORD);
};
const loginWithWCv2 = async () => {
if (!web3auth) {
uiConsole('web3auth not initialized yet');
return;
}
try {
const web3authProvider = await web3auth.connectTo(WALLET_ADAPTERS.WALLET_CONNECT_V2);
setProvider(web3authProvider);
} catch (e) {
console.error(e);
walletConnectModal.closeModal();
if (!e?.toString?.()?.includes?.('User closed modal')) {
setError(e?.toString?.() || 'Internal error');
}
}
};
const loginWithMM = async () => {
popUpClosedByUser.current = false;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (window && typeof window.ethereum == 'undefined') {
console.error('MetaMask is not installed!');
setError('MetaMask is not installed!');
return;
}
if (!web3auth) {
uiConsole('web3auth not initialized yet');
return;
}
setIsConnecting(true);
try {
const web3authProvider = await web3auth.connectTo(WALLET_ADAPTERS.METAMASK);
setProvider(web3authProvider);
} catch (e) {
console.error(e);
if ((e as any)?.message.includes('Failed to connect with wallet. Already connected')) {
setError((e as any)?.message);
}
if (!popUpClosedByUser.current) setError(e?.toString?.() || 'Internal error');
} finally {
setIsConnecting(false);
}
};
const authenticateUser = async () => {
if (!web3auth) {
uiConsole('web3auth not initialized yet');
return;
}
const idToken = await web3auth.authenticateUser();
return idToken;
};
const logout = async () => {
if (!web3auth) {
uiConsole('web3auth not initialized yet');
return;
}
// @TODO
await web3auth.logout();
setProvider(null);
setUserInfo(null);
setAuthInfo({});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function uiConsole(...args: any[]): void {
const message = JSON.stringify(args || {}, null, 2);
console.log(message);
}
return {
web3auth,
torusPlugin,
provider,
authenticateUser,
logout,
isConnecting,
loginWithGoogle,
loginWithFacebook,
loginWithTwitter,
loginWithDiscord,
loginWithWCv2,
loginWithMM,
error,
setError,
isLoading: isLoading.current,
socialLogin,
userInfo,
authInfo,
isUserInfoLoading,
};
};
Related file used in the hook: constants/web3Auth
import { CHAIN_NAMESPACES } from '@web3auth/base';
import { OPENLOGIN_NETWORK, LANGUAGE_TYPE } from '@web3auth/openlogin-adapter';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const config: Record<string, any> = {
[OPENLOGIN_NETWORK.TESTNET]: {
chainNamespace: CHAIN_NAMESPACES.EIP155,
chainId: '0x13881',
rpcTarget: 'https://rpc-mumbai.maticvigil.com',
displayName: 'Polygon Mumbai Testnet',
blockExplorer: 'https://mumbai.polygonscan.com/',
ticker: 'MATIC',
tickerName: 'Polygon Mumbai',
},
[OPENLOGIN_NETWORK.MAINNET]: {
chainNamespace: CHAIN_NAMESPACES.EIP155,
chainId: '0x89',
rpcTarget: 'https://rpc-mainnet.maticvigil.com',
displayName: 'Polygon Mainnet',
blockExplorer: 'https://polygonscan.com/',
ticker: 'MATIC',
tickerName: 'Polygon Mainnet',
},
};
export const chainConfig =
config[process.env.REACT_APP_CHAIN_CONFIG_NETWORK || OPENLOGIN_NETWORK.TESTNET];
const CHAIN_ID = Number(chainConfig.chainId);
export const WC_CHAIN_IDS = [CHAIN_ID];
export const openLoginAdapterSettings = {
whiteLabel: {
defaultLanguage: 'en' as LANGUAGE_TYPE,
mode: 'dark',
theme: {
primary: '#00D1B2',
},
},
};
export const SOCIAL_AUTH_PROVIDER_MAP = {
FACEBOOK: 'facebook',
GOOGLE: 'google',
DISCORD: 'discord',
TWITTER: 'twitter',
} as const;
export const SOCIAL_AUTH_PROVIDERS = Object.values(SOCIAL_AUTH_PROVIDER_MAP);
Related file used in the hook: @utils/web3auth.utils
import { getPublicCompressed } from '@toruslabs/eccrypto';
import { SafeEventEmitterProvider } from '@web3auth/base';
import { Web3AuthNoModal } from '@web3auth/no-modal';
import { LOGIN_PROVIDER_TYPE, OpenloginUserInfo } from '@web3auth/openlogin-adapter';
import RPC from '@services/web3RPC.service';
import { SOCIAL_AUTH_PROVIDERS } from '@/constants/web3Auth';
export const getAppPrivKey = async (provider: SafeEventEmitterProvider) => {
return provider.request({
method: 'eth_private_key', // use "private_key" for other non-evm chains
}) as Promise<string>;
};
export const getAppPubKey = async (provider: SafeEventEmitterProvider) => {
const app_scoped_privkey = await getAppPrivKey(provider);
return getPublicCompressed(Buffer.from(app_scoped_privkey.padStart(64, '0'), 'hex')).toString(
'hex',
);
};
export const getWalletAddress = async (provider: SafeEventEmitterProvider) => {
const rpc = new RPC(provider);
return rpc.getAccounts() as Promise<string>;
};
export const getLoginProvider = async (
web3auth: Web3AuthNoModal,
userInfo?: Partial<OpenloginUserInfo>,
) => {
const userInfoRaw = userInfo ?? (await web3auth.getUserInfo());
const loginProvider = userInfoRaw.typeOfLogin ?? web3auth?.connectedAdapterName ?? '';
return loginProvider as LOGIN_PROVIDER_TYPE;
};
export const getAuthInfo = async (
web3auth: Web3AuthNoModal,
userInfo?: Partial<OpenloginUserInfo>,
) => {
let appPubKey = '',
authToken = '',
email = '',
loginProvider = '';
const userInfoRaw = userInfo ?? (await web3auth.getUserInfo());
loginProvider = await getLoginProvider(web3auth, userInfoRaw);
email = userInfoRaw?.email ?? '';
// only return pubkey and authToken for social login
if (SOCIAL_AUTH_PROVIDERS.includes(loginProvider as any)) {
appPubKey = await getAppPubKey(web3auth.provider!);
authToken = userInfoRaw?.idToken ?? '';
}
return {
appPubKey,
authToken,
email,
loginProvider,
userInfoRaw,
};
};
Related implementation in @services/web3RPC.service
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { SafeEventEmitterProvider } from '@web3auth/base';
import { ethers } from 'ethers';
export default class EthereumRpc {
private provider: SafeEventEmitterProvider;
constructor(provider: SafeEventEmitterProvider) {
this.provider = provider;
}
async getAccounts(): Promise<any> {
try {
const ethersProvider = new ethers.providers.Web3Provider(this.provider);
const signer = ethersProvider.getSigner();
// Get user's Ethereum public address
const address = await signer.getAddress();
return address;
} catch (error) {
return error;
}
}
}
related packages
"@web3auth/base": "^7.0.0",
"@web3auth/ethereum-provider": "^7.0.0",
"@web3auth/metamask-adapter": "^7.0.0",
"@web3auth/no-modal": "^7.0.0",
"@web3auth/openlogin-adapter": "^7.0.0",
"@web3auth/torus-wallet-connector-plugin": "^7.0.0",
"@web3auth/wallet-connect-v2-adapter": "^7.0.0",
"ethers": "^5.7.2",
"@walletconnect/modal": "^2.6.2",
"@toruslabs/eccrypto": "^4.0.0",