Questions regarding the legacy testnet project for the SFA Node SDK

Hello, I have some questions.

Previously, my project used the legacy testnet with Google login via Web3Auth No Modal without custom authentication.

However, we now need to use the SFA Node SDK for our backend, which requires custom authentication as a verifier according to the documentation.

Here are my questions:

  1. Can my project’s legacy testnet be switched to sapphire_devnet?
  2. Does the SFA Node SDK support the legacy testnet?
  3. When using the SFA Node SDK to create custom authentication for Google verifiers, will the resulting wallet be different from the one obtained via Google login with Web3Auth No Modal?

Thank you.

Hey @dev2

  1. The switching from legacy testnet to sapphire_devnet might require efforts from our side and additional fees, can confirm with the team.
  2. Yes, SFA Node SDK has support for the legacy testnet
  3. You are correct, the address would be different than one obtained via No Modal SDK. In general also the key would be different unless you use the usePnPKey true in SFA.

Hello, thank you for the information. I have tried using the SFA Node with usePnPkey: true and tested it in the “mainnet” / legacy mainnet environment using the same Google email. However, it results in different wallets between Web3Auth on the frontend side of the current project and the SFA Node. Could you please provide a solution for this?

Below is the code I am using for Web3Auth in the frontend React and in the SFA Node.

Note: The SFA Node is still in development, so the idToken and email are hardcoded for testing purposes.

WEB3AUTH CONFIG ON REACT/FRONT END CODE (that is currently running)

Dependencies:

  "@web3auth/base": "^6.1.7",
    "@web3auth/ethereum-provider": "^6.1.7",
    "@web3auth/metamask-adapter": "^6.1.7",
    "@web3auth/no-modal": "^6.1.7",
    "@web3auth/openlogin-adapter": "^6.1.7",
    "@web3auth/torus-wallet-connector-plugin": "^6.1.7",
    "@web3auth/wallet-connect-v2-adapter": "^6.1.7"

Code :

const web3AuthKey = process.env.WEB3_AUTH_KEY;
    const chainConfig = {
          chainNamespace: CHAIN_NAMESPACES.EIP155,
          chainId: ethers.utils.hexValue(ethereumChain.id),
          rpcTarget: getChainConfig(ethereumChain.id).rpcUrls?.alchemy?.http[0]
            ? `${getChainConfig(ethereumChain.id).rpcUrls.alchemy.http[0]}/${process.env.ALCHEMY_API_KEY}`
            : getChainConfig(ethereumChain.id).rpcUrls.default?.http[0],
          displayName: ethereumChain.name,
          blockExplorer: ethereumChain.blockExplorers.default?.url,
          ticker: ethereumChain.nativeCurrency?.symbol,
          tickerName: ethereumChain.nativeCurrency?.name,
        };

        const web3AuthInstance_ = new Web3AuthNoModal({
          clientId: web3AuthKey,
          chainConfig: chainConfig,
          web3AuthNetwork: process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' ? 'mainnet' : 'testnet',
        });
        const privateKeyProvider = new EthereumPrivateKeyProvider({ config: { chainConfig } });
        const openloginAdapter = new OpenloginAdapter({
          privateKeyProvider,
          loginSettings: {
            mfaLevel: 'none',
          },
          adapterSettings: {
            network: defaultNetwork,
            uxMode: 'redirect',
            replaceUrlOnRedirect: true,
            whiteLabel: {
              name: 'Gaspack App',
              logoLight: '/images/logo-gaspack-dark.png',
              logoDark: '/images/logo-gaspack-light.png',
              defaultLanguage: 'en',
              dark: true,
            },
          },
        });

        const metamaskAdapter = new MetamaskAdapter({
          web3AuthNetwork: process.env.NEXT_PUBLIC_VERCEL_ENV === 'production' ? 'mainnet' : 'testnet',
        });

        const walletConnectV2Adapter = await (async () => {
          const defaultWcSettings = await getWalletConnectV2Settings(
            'eip155',
            [1, 5, 137, 80001],
            '6c5e637429bc2ce328a662d672e391d6',
          );

          return new WalletConnectV2Adapter({
            web3AuthNetwork: defaultNetwork,
            adapterSettings: { qrcodeModal: QRCodeModal, ...defaultWcSettings.adapterSettings },
            loginSettings: { ...defaultWcSettings.loginSettings },
          });
        })();

        web3AuthInstance_.configureAdapter(openloginAdapter);
        web3AuthInstance_.configureAdapter(walletConnectV2Adapter);
        web3AuthInstance_.configureAdapter(metamaskAdapter);

        const torusPlugin = new TorusWalletConnectorPlugin({
          torusWalletOpts: {},
          walletInitOptions: {
            whiteLabel: {
              theme: { isDark: true, colors: { primary: '#4200ff' } },
              logoLight: 'https://app.gaspack.xyz/images/logo-gaspack-light.png',
              logoDark: 'https://app.gaspack.xyz/images/logo-gaspack-dark.png',
              topupHide: true,
              featuredBillboardHide: true,
              disclaimerHide: true,
            },
            useWalletConnect: true,
            showTorusButton: false,
            enableLogging: true,
          },
        });

        setTorusPlugin(torusPlugin);
        await web3AuthInstance_.addPlugin(torusPlugin);

        subscribeAuthEvents(web3AuthInstance_);
        await web3AuthInstance_.init();

WEB3AUTH CONFIG ON SFA NODE

Depedencies

"@wagmi/chains": "^1.8.0",
    "@web3auth/base": "^8.8.0",
    "@web3auth/ethereum-provider": "^8.10.2",
    "@web3auth/node-sdk": "^3.3.0",
    "@web3auth/openlogin-adapter": "^8.8.0",

Code :

/* eslint-disable import/no-extraneous-dependencies */
import type { CustomChainConfig } from '@web3auth/base';
import { CHAIN_NAMESPACES } from '@web3auth/base';
import { EthereumPrivateKeyProvider } from '@web3auth/ethereum-provider';
import { Web3Auth } from '@web3auth/node-sdk';
import { ethers } from 'ethers';
import fs from 'fs';
import type { NextApiRequest, NextApiResponse } from 'next';

import { ethereumChain, getChainConfig } from '@/config/chainConfig';

const privateKey = fs.readFileSync('privateKey.pem');

type Data =
  | {
      ethPrivateKey: string;
      ethAddress: string;
    }
  | {
      error: string;
    };

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  try {
    const web3AuthKey = process.env.NEXT_PUBLIC_WEB3_AUTH_KEY;

    const web3auth = new Web3Auth({
      clientId: web3AuthKey as string,
      web3AuthNetwork: 'mainnet', // Get your Network ID from Web3Auth Dashboard
      usePnPKey: true,
    });

    const chainConfig: CustomChainConfig = {
      chainNamespace: CHAIN_NAMESPACES.EIP155,
      chainId: ethers.utils.hexValue(ethereumChain.id),
      rpcTarget: getChainConfig(ethereumChain.id)?.rpcUrls?.alchemy?.http?.[0]
        ? `${getChainConfig(ethereumChain.id).rpcUrls.alchemy.http[0]}/${
            process.env.ALCHEMY_API_KEY
          }`
        : getChainConfig(ethereumChain.id)?.rpcUrls?.default?.http?.[0],
      displayName: ethereumChain.name,
      blockExplorerUrl: ethereumChain.blockExplorers.default?.url,
      ticker: ethereumChain.nativeCurrency?.symbol,
      tickerName: ethereumChain.nativeCurrency?.name,
    };

    const ethereumProvider = new EthereumPrivateKeyProvider({
      config: {
        chainConfig,
      },
    });

    web3auth.init({ provider: ethereumProvider });
 
    const idTokenGoogle =
     'eyJhbGciOiJSUzI1NiIsImtpZCI6ImYyZTExOTg2MjgyZGU5M2YyN2IyNjRmZDJhNGRlMTkyOTkzZGNiOGMiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI1MDg1OTM0ODg1NDQtMmplMXByZmNrbnAzbjZiaGxiNnNwbGtwYTAyZnQwZGUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI1MDg1OTM0ODg1NDQtMmplMXByZmNrbnAzbjZiaGxiNnNwbGtwYTAyZnQwZGUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDAzNTAxNTgyMDE2MTMwMjY4MzYiLCJoZCI6Imdhc3BhY2sueHl6IiwiZW1haWwiOiJjaGFuZHJhQGdhc3BhY2sueHl6IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJvdldFakRFaEI5OW5kczFfSDNaYzFBIiwibmFtZSI6IkNoYW5kcmEgTXVoYW1hZCBBcHJpYW5hIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hL0FDZzhvY0luYmtRUmpkOXM4SlF0ZG15eG8zQmhJTjdvNTVuUmgtVHpGYVdDMlFDck5mNHpUZz1zOTYtYyIsImdpdmVuX25hbWUiOiJDaGFuZHJhIE11aGFtYWQiLCJmYW1pbHlfbmFtZSI6IkFwcmlhbmEiLCJpYXQiOjE3MjE5MTQwMzcsImV4cCI6MTcyMTkxNzYzN30.WR5Um6k02jaSw8OTTRW9LDNGBa-wRGPM2GKfx6WB48477re0NuuaFWwn2GiJFMTcQjOlGcG17suHco0cNK_sOIRI0f_vgJEhIA1sYyMOuLOefnnXJHZbBoiQ0qiMQAiPoLkdsRh44EbVFzaLcQiX4XZBVVnQQZHB1F9Rh0tCjxD8VTwSheaff84EY_aKb9YyvaB2GDIDbw_MHFFz1IBGMC6y9xkZmyFuDEcmXrVm-PauziWyUO-xCJP8HCL2dyTxO4hGs2xZPoTZbTkHItbfqVyclNPKjgjFciKj9jsWqm9PxyTw3WfnI1d5gfpXxJmI3pZpA4RKYNsf-rLeZlsyIA';
   
    const provider = await web3auth.connect({
      verifier:  process.env.NEXT_PUBLIC_WEB3_AUTH_VERIFIER;, 
      verifierId: 'chandra@gaspack.xyz', 
      idToken: idTokenGoogle,
    });
   
    if (!provider) {
      throw new Error('Failed to connect to provider');
    }

    const ethPrivateKey = (await provider.request({
      method: 'eth_private_key',
    })) as string;
    const ethAddress = (await provider.request({
      method: 'eth_accounts',
    })) as string[];

    res.status(200).json({ ethPrivateKey, ethAddress: ethAddress[0] });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Failed to connect using Web3Auth' });
  }
}

CHAIN CONFIG

/* eslint-disable import/no-extraneous-dependencies */
import { mainnet, polygon, polygonMumbai, sepolia } from '@wagmi/chains';
import { Network } from 'alchemy-sdk';
import { ethers } from 'ethers';

import { ghostnetTezos, tezos } from './customChainConfig';

const isProduction = process.env.NEXT_PUBLIC_STAGE === 'production';
export const ethereumChain = isProduction ? mainnet : sepolia;
export const polygonChain = isProduction ? polygon : polygonMumbai;
export const tezosChain = isProduction ? tezos : ghostnetTezos;
export const defaultNetwork = isProduction ? 'mainnet' : 'testnet';

export function getChainConfig(chainId: number | string) {
  switch (chainId) {
    case mainnet.id:
      return {
        ...mainnet,
        openSea: {
          subDomain: null,
          networkName: 'ethereum',
        },
        alchemySetting: {
          apiKey: process.env.ALCHEMY_API_KEY,
          network: Network.ETH_MAINNET,
        },
        estimatedMinimumTransactionFee: 0.001,
        gasPriceLimit: ethers.utils.parseUnits('100', 'gwei'),
        blockchain: 'evm',
        rpcUrls: {
          alchemy: {
            http: ['https://eth-mainnet.g.alchemy.com/v2'],
            webSocket: ['wss://eth-mainnet.g.alchemy.com/v2'],
          },
          infura: {
            http: ['https://mainnet.infura.io/v3'],
            webSocket: ['wss://mainnet.infura.io/ws/v3'],
          },
          default: {
            http: [`https://cloudflare-eth.com"`],
          },
          public: {
            http: [`https://cloudflare-eth.com"`],
          },
        },
      };
    case sepolia.id:
      return {
        ...sepolia,
        openSea: {
          subDomain: 'testnets',
          networkName: 'sepolia',
        },
        alchemySetting: {
          apiKey: process.env.ALCHEMY_API_KEY,
          network: Network.ETH_SEPOLIA,
        },
        estimatedMinimumTransactionFee: 0.001,
        gasPriceLimit: ethers.utils.parseUnits('100', 'gwei'),
        blockchain: 'evm',
        rpcUrls: {
          alchemy: {
            http: ['https://eth-sepolia.g.alchemy.com/v2'],
            webSocket: ['wss://eth-sepolia.g.alchemy.com/v2'],
          },
          infura: {
            http: ['https://sepolia.infura.io/v3'],
            webSocket: ['wss://sepolia.infura.io/ws/v3'],
          },
          default: {
            http: [`wss://sepolia.infura.io/ws/v3`],
          },
          public: {
            http: [`wss://sepolia.infura.io/ws/v3`],
          },
        },
      };
    case polygon.id:
      return {
        ...polygon,
        rpcUrls: {
          alchemy: {
            http: ['https://polygon-mainnet.g.alchemy.com/v2'],
            webSocket: ['wss://polygon-mainnet.g.alchemy.com/v2'],
          },
          infura: {
            http: ['https://polygon-mainnet.infura.io/v3'],
            webSocket: ['wss://polygon-mainnet.infura.io/ws/v3'],
          },
          default: {
            http: [
              `https://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY_POLYGON}`,
            ],
          },
          public: {
            http: [
              `https://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY_POLYGON}`,
            ],
          },
        },
        openSea: {
          subDomain: null,
          networkName: 'matic',
        },
        alchemySetting: {
          apiKey: process.env.ALCHEMY_API_KEY_POLYGON,
          network: Network.MATIC_MAINNET,
        },
        estimatedMinimumTransactionFee: 0.1,
        gasPriceLimit: ethers.utils.parseUnits('1000', 'gwei'),
        blockchain: 'evm',
      };
    case polygonMumbai.id:
      return {
        ...polygonMumbai,
        rpcUrls: {
          alchemy: {
            http: ['https://polygon-mumbai.g.alchemy.com/v2'],
            webSocket: ['wss://polygon-mumbai.g.alchemy.com/v2'],
          },
          infura: {
            http: ['https://polygon-mumbai.infura.io/v3'],
            webSocket: ['wss://polygon-mumbai.infura.io/ws/v3'],
          },
          default: {
            http: [
              `https://polygon-mumbai.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY_POLYGON}`,
            ],
          },
          public: {
            http: [
              `https://polygon-mumbai.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY_POLYGON}`,
            ],
          },
        },
        openSea: {
          subDomain: 'testnets',
          networkName: 'mumbai',
        },
        alchemySetting: {
          apiKey: process.env.ALCHEMY_API_KEY_POLYGON,
          network: Network.MATIC_MUMBAI,
        },
        estimatedMinimumTransactionFee: 0.1,
        gasPriceLimit: ethers.utils.parseUnits('1000', 'gwei'),
        blockchain: 'evm',
      };
    case ghostnetTezos.id:
      return {
        ...ghostnetTezos,
        openSea: {
          subDomain: 'testnet',
          networkName: 'tezos',
        },
        alchemySetting: {
          apiKey: process.env.ALCHEMY_API_KEY,
          network: Network.ETH_MAINNET,
        },
        estimatedMinimumTransactionFee: 0.001,
        gasPriceLimit: ethers.utils.parseUnits('100', 'gwei'),
        blockchain: 'tezos',
      };
    case tezos.id:
      return {
        ...tezos,
        openSea: {
          subDomain: '',
          networkName: 'tezos',
        },
        alchemySetting: {
          apiKey: process.env.ALCHEMY_API_KEY,
          network: Network.ETH_MAINNET,
        },
        estimatedMinimumTransactionFee: 0.001,
        gasPriceLimit: ethers.utils.parseUnits('100', 'gwei'),
        blockchain: 'tezos',
      };
    default:
      return {
        ...mainnet,
        openSea: {
          subDomain: null,
          networkName: 'ethereum',
        },
        alchemySetting: {
          apiKey: process.env.ALCHEMY_API_KEY,
          network: Network.ETH_MAINNET,
        },
        estimatedMinimumTransactionFee: 0.001,
        gasPriceLimit: ethers.utils.parseUnits('100', 'gwei'),
        blockchain: 'evm',
      };
  }
}

Hi @Ayush , is there any update regarding this?

So, from the code I can see that you are using the default login provided by the SDK, please correct me if wrong. And in SFA node, you are using your own google authentication and then passing the idToken to login.

For usePnPKey to work, there have to be base condition that you are using the same verifier. For instance, if you are using the default verifier in No Modal, then it’ll be using our own google client id, and for SFA node, your google client id is different. So, even if you are using same email, it won’t work and generate the same account because google client id would be different, and primarily the verifier would be different. The address that are generated are mapped to a particular verifier. If you are using the default verifier, that means you are using Web3Auth Global Verifier. So if there is any DApp out there using the same default verifier google for instance, you would be able to get the same account in different DApps.

For SFA, you have created your own verifier, which means the address generated is linked to your Web3Auth project. You can use the same verifier in PnP No Modal, and you would be getting same address. You can check the example here to use Custom Auth in No Modal: Custom Authentication with PnP Web No Modal SDK | Documentation | Web3Auth

thanks @Ayush what you mentioned is correct, then is there a way to use Web3Auth Global Verifier on SFA Node? because we already have users on Web3Auth Global Verifier

As of now I don’t think there’s a way to use Global verifier on SFA node, as for global verifier, the client id, and console is managed by us. And for SFA you need to explicitly login first, and pass the idToken so Web3Auth nodes can verify. Setting up your own Google id for authentication would solve the issue.

Is there a way to work around this so I can use it in SFA Node? Maybe migrating users? Because we already have about 90k users in Web3AuthNoModal (global verifier). @Ayush