Need help switching from Modal SDK to NoModal SDK

Hello,

I’ve posted this as multiple topics here but haven’t received an answer and this is rather time critical for us.

Context:

  • We are using web3auth-wagmi-connector.
  • We are using web3Auth.authenticatedUser() and verifying signature on our backend with jwks.
  • We support multiple openlogin providers (email_passwordless, google, apple, reddit, X) and MetaMask.

Goal:
We need to switch from Modal SDK to NoModal SDK to support an embedded view in our app.

Issue 1: “autoConnect” not working
Solved by updating to web3auth v8 and web3auth-wagmi-connector v6, thank you @TomTom

Issue 2: Error when trying to log in using MetaMask.
The web3auth redirect page opens with the following error: Invalid loginProvider or custom loginProvider data not available We are using the following config:

    Web3AuthConnector({
      web3AuthInstance,
      loginParams: {
        loginProvider: WALLET_ADAPTERS.METAMASK,
      },
    }),

Issue 3: Cannot find a way to dynamically connect to different OpenLogin providers.
The loginParams have to be passed when initializing Web3AuthConnector, which is impossible when using email_passwordless because the user email is not known beforehand:

    Web3AuthConnector({
      web3AuthInstance,
      // Cannot pass this directly at initialization
      loginParams: {
        loginProvider: LOGIN_PROVIDER.EMAIL_PASSWORDLESS,
        extraLoginOptions: {
          login_hint: 'address@email.com'
        },
      },
    }),

The web3AuthInstance offers the following method to specify the connector:

await web3auth.connectTo(WALLET_ADAPTERS.OPENLOGIN, {
      loginProvider: "google",
    });

How to do the same using wagmi?

  const {connect} = useConnect();
  const onConnect = () => {
    // Where to put loginProvider: "google" ?
    connect({connector: web3AuthConnector});
  }

Thank you!

connect({connector: Web3AuthConnectorInstance([mainnet, sepolia, polygon])})}

you can check the example where yo can change the loginProvider

I was able to get this working by modifying the web3auth-wagmi-connector source code and passing the connector in the connect function:

  const connectWithWeb3Auth = loginParams => {
    connect({
      chainId: chains[0].id,
      connector: CustomWeb3AuthConnector({
        web3AuthInstance,
        walletName: loginParams.walletName,
        loginParams: loginParams.loginParams,
      }),
    });

Here is the modified source code that allows connecting to MetaMask:

import {ChainNotConfiguredError, createConnector, normalizeChainId} from '@wagmi/core';
import type {IProvider, IWeb3Auth, WALLET_ADAPTER_TYPE} from '@web3auth/base';
import {ADAPTER_STATUS, CHAIN_NAMESPACES, log} from '@web3auth/base';
import type {OpenloginLoginParams} from '@web3auth/openlogin-adapter';
import {Chain, Hex, SwitchChainError, UserRejectedRequestError, getAddress} from 'viem';

export interface Web3AuthConnectorParams {
  web3AuthInstance: IWeb3Auth;
  walletName: WALLET_ADAPTER_TYPE;
  loginParams?: OpenloginLoginParams;
}

export const CustomWeb3AuthConnector = (parameters: Web3AuthConnectorParams) => {
  let walletProvider: IProvider | null = null;

  const {web3AuthInstance, walletName, loginParams} = parameters;

  return createConnector<IProvider>(config => ({
    id: 'web3auth',
    name: 'Web3Auth',
    type: 'Web3Auth',
    async connect({chainId} = {}) {
      try {
        config.emitter.emit('message', {
          type: 'connecting',
        });
        const provider = await this.getProvider();

        provider.on('accountsChanged', this.onAccountsChanged);
        provider.on('chainChanged', this.onChainChanged);
        provider.on('disconnect', this.onDisconnect.bind(this));

        if (!web3AuthInstance.connected) {
          await web3AuthInstance.connectTo(walletName, loginParams ?? undefined);
        }

        let currentChainId = await this.getChainId();
        if (chainId && currentChainId !== chainId) {
          const chain = await this.switchChain!({chainId}).catch(error => {
            if (error.code === UserRejectedRequestError.code) throw error;
            return {id: currentChainId};
          });
          currentChainId = chain?.id ?? currentChainId;
        }

        const accounts = await this.getAccounts();

        return {accounts, chainId: currentChainId};
      } catch (error) {
        log.error('error while connecting', error);
        this.onDisconnect();
        throw new UserRejectedRequestError('Something went wrong' as unknown as Error);
      }
    },
    async getAccounts(): Promise<Hex[]> {
      const provider = await this.getProvider();
      const result = await provider.request<{method: string}, string[]>({
        method: 'eth_accounts',
      });
      if (!Array.isArray(result)) {
        throw new Error('Invalid accounts result');
      }
      // @ts-ignore
      return result.map((x: string) => getAddress(x));
    },
    async getChainId() {
      const provider = await this.getProvider();
      const chainId = await provider.request<unknown, number>({method: 'eth_chainId'});
      return normalizeChainId(chainId);
    },
    async getProvider(): Promise<IProvider> {
      if (walletProvider) {
        return walletProvider;
      }
      if (web3AuthInstance.status === ADAPTER_STATUS.NOT_READY) {
        await web3AuthInstance.init();
      }

      walletProvider = web3AuthInstance.provider;

      if (!walletProvider) {
        throw new Error('Provider not found');
      }

      return walletProvider;
    },
    async isAuthorized() {
      try {
        const accounts = await this.getAccounts();
        return !!accounts.length;
      } catch {
        return false;
      }
    },
    async switchChain({chainId}): Promise<Chain> {
      try {
        const chain = config.chains.find(x => x.id === chainId);
        if (!chain) throw new SwitchChainError(new ChainNotConfiguredError());

        await web3AuthInstance.addChain({
          chainNamespace: CHAIN_NAMESPACES.EIP155,
          chainId: `0x${chain.id.toString(16)}`,
          rpcTarget: chain.rpcUrls.default.http[0],
          displayName: chain.name,
          blockExplorerUrl: chain.blockExplorers?.default.url[0] || '',
          ticker: chain.nativeCurrency?.symbol || 'ETH',
          tickerName: chain.nativeCurrency?.name || 'Ethereum',
          decimals: chain.nativeCurrency?.decimals || 18,
          logo: chain.nativeCurrency?.symbol
            ? `https://images.toruswallet.io/${chain.nativeCurrency?.symbol.toLowerCase()}.svg`
            : 'https://images.toruswallet.io/eth.svg',
        });
        log.info('Chain Added: ', chain.name);
        await web3AuthInstance.switchChain({chainId: `0x${chain.id.toString(16)}`});
        log.info('Chain Switched to ', chain.name);
        config.emitter.emit('change', {
          chainId,
        });
        return chain;
      } catch (error: unknown) {
        log.error('Error: Cannot change chain', error);
        throw new SwitchChainError(error as Error);
      }
    },
    async disconnect(): Promise<void> {
      await web3AuthInstance.logout();
      const provider = await this.getProvider();
      provider.removeListener('accountsChanged', this.onAccountsChanged);
      provider.removeListener('chainChanged', this.onChainChanged);
    },
    onAccountsChanged(accounts) {
      if (accounts.length === 0) config.emitter.emit('disconnect');
      else
        config.emitter.emit('change', {
          accounts: accounts.map(x => getAddress(x)),
        });
    },
    onChainChanged(chain) {
      const chainId = normalizeChainId(chain);
      config.emitter.emit('change', {chainId});
    },
    onDisconnect(): void {
      config.emitter.emit('disconnect');
    },
  }));
};

How to keep the connection across page refresh using this method?

hi @afarley,
try using useState for web3AuthInstance. Please check our examples.