Web3Auth + Ethers.js: Stuck on 'Cannot Convert Undefined to BigInt' Error During USDC Transfer

I’m encountering an issue while implementing a USDC transfer functionality in my Next.js project integrated with Web3Auth. Here’s the situation:

Description of the Issue:

The transfer function is failing with the following error in the console:
"Error: could not coalesce error (error={ “message”: “Cannot convert undefined to a BigInt” }, payload={ “id”: 5, “jsonrpc”: “2.0”, “method”: “eth_sendTransaction”, “params”: [ { “data”: “0xa9059cbb00000000000000000000000045c123dbaa105183e491073341c9c061824e938f00000000000000000000000000000000000000000000000000000000006acfc0”, “from”: “0x9979ac07d83a3a3de78c71b7cd90718293f7fee5”, “gas”: “0x186a0”, “to”: “0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582” } ] }, code=UNKNOWN_ERROR, version=6.13.4)
"

Steps to Reproduce the Issue:

  1. Visit the deployed app at: https://web3auth-nextjs-project.vercel.app.
  2. Log in using any social authentication method.
  3. Copy the wallet address provided in the app.
  4. Obtain some USDC tokens from https://faucet.circle.com (Polygon Amoy Testnet).
  5. Return to the wallet interface in the app.
  6. Input the recipient address (we used a MetaMask wallet on the Polygon Amoy Testnet).
  7. Input an amount to transfer and click the “Send” button.
  8. Observe the error in the browser console.

Environment Details:

  • Next.js version: 15
  • Web3Auth SDK version: 9
  • Dependencies: Ethers.js version 6
  • Blockchain Network: Polygon Amoy Testnet

Doubts and Observations:

I suspect the error may be related to either:

  1. An issue with the Web3Auth provider initialization or account abstraction provider setup.
  2. Misconfiguration in the transaction payload, particularly the data or gas parameters.
  3. An Ethers.js-specific incompatibility or misuse of the BigInt type.

Questions:

  • Has anyone encountered this error before?
  • Is this issue related to Web3Auth’s provider setup, or could it be a misstep in my implementation?

Code and Deployment:

Request for Suggestions:

I’d appreciate guidance on:

  1. Debugging steps to verify if the issue is related to Web3Auth or Ethers.js.
  2. Any known fixes or best practices to handle BigInt conversion errors in such scenarios.

Thanks in advance for your support!

Hi Friday Ameh Matthew! Thank you for reaching out and providing such detailed information about the issue you’re facing with the USDC transfer functionality in your Next.js project. Let’s break down the information for the support team:

Reported Issue Details:

  • SDK Details: PnP Web SDK
  • SDK Version Number: 9
  • Platform: Next.js 15
  • Dependencies: Ethers.js version 6
  • Blockchain Network: Polygon Amoy Testnet

Error Encountered:

  • Console Error Message: “Cannot convert undefined to a BigInt” during USDC transfer.

Steps to Reproduce:

  1. Visit: Deployed App
  2. Log in using any social authentication method.
  3. Copy the wallet address provided.
  4. Obtain USDC tokens from Circle Faucet.
  5. Input the MetaMask wallet address.
  6. Specify the transfer amount and click “Send”.
  7. Observe the error in the browser console.

Doubts and Observations:

  • Suspected issues with:
    1. Web3Auth provider initialization or account abstraction provider setup.
    2. Misconfiguration in the transaction payload.
    3. Ethers.js-specific issues with BigInt.

Request for Guidance:

  • Debugging steps for identifying whether the issue is with Web3Auth or Ethers.js.
  • Known fixes or best practices to prevent BigInt conversion errors.

Links Provided:

Proposed Solutions:

To help you begin troubleshooting:

  1. Double-check the data, from, and gas parameters in the transaction payload. Ensure they have appropriate values before the transaction is initiated.
  2. Review the Web3Auth provider initialization to confirm correct setup, especially if you’re using custom authentication.
  3. If using Ethers.js, make sure to validate the inputs before passing them into functions that expect BigInt values.

Feel free to reach out if you need further assistance. Good luck, and I hope you find the solution soon!

Note: This response was generated by an AI bot and may require further verification. The Web3Auth support team has tracked this issue and will be responding as soon as possible.

Hey @fridayamehmatthew please provide the code snippet for the USDC transfer, the Gitlab repo link is not clickable.

Hi Ayush,
So sorry for the inconvenience, below is the code snippet and the Gitlab repo. Friday Ameh Matthew / Web3auth Nextjs Project · GitLab

Code snippet: page.tsx

"use client";

// Importing necessary packages and components
import { CHAIN_NAMESPACES, IAdapter, IProvider, WEB3AUTH_NETWORK } from "@web3auth/base";
import { Box, Paper, Typography, Button, TextField } from "@mui/material";
import { EthereumPrivateKeyProvider } from "@web3auth/ethereum-provider";
import { Web3Auth, Web3AuthOptions } from "@web3auth/modal";
import { AccountAbstractionProvider, SafeSmartAccount } from "@web3auth/account-abstraction-provider";
import { ethers } from "ethers"; // Used for blockchain interaction
import { formatUnits, parseUnits } from "ethers"; // Utility functions for token formatting
import { useEffect, useState, useRef } from "react"; // React hooks for managing state and lifecycle

// Web3Auth client ID for initialization
const clientId = "BNUKnfjxWu4bIu2S9XLdLm102AL6Lq2aLlyDIsBX77Qo81ztaO7UkUyBdpOJuuqaUnY9ugqn3Q3aeBZElwSTdvk";

// API key for Pimlico (bundler and paymaster)
const pimlicoAPIKey = "pim_NQiLku6tPP9FW3Tn7B78JH";

// USDC contract address on Polygon Amoy Testnet (replace with the correct address for other networks)
const usdcContractAddress = "0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582";

// Configuration for the blockchain (Polygon Amoy Testnet)
const chainConfig = {
  chainNamespace: CHAIN_NAMESPACES.EIP155, // Ethereum-compatible namespace
  chainId: "0x13882", // Chain ID for Polygon Amoy Testnet
  rpcTarget: "https://rpc.ankr.com/polygon_amoy", // RPC URL for interacting with the chain
  displayName: "Polygon Amoy Testnet", // Friendly name for the chain
  ticker: "MATIC", // Currency ticker
  tickerName: "Matic", // Currency full name
};

// Ethereum provider for private key-based interaction
const privateKeyProvider = new EthereumPrivateKeyProvider({
  config: { chainConfig },
});

// Account abstraction provider configuration
const accountAbstractionProvider = new AccountAbstractionProvider({
  config: {
    chainConfig,
    smartAccountInit: new SafeSmartAccount(), // Safe smart account initialization
    bundlerConfig: {
      url: `https://api.pimlico.io/v2/13882/rpc?apikey=${pimlicoAPIKey}`, // Bundler API URL
    },
    paymasterConfig: {
      url: `https://api.pimlico.io/v2/13882/rpc?apikey=${pimlicoAPIKey}`, // Paymaster API URL
    },
  },
});

function App() {
  // State variables
  const [provider, setProvider] = useState<IProvider | null>(null); // Blockchain provider
  const [usdcBalance, setUsdcBalance] = useState<string>(""); // USDC balance
  const [walletAddress, setWalletAddress] = useState<string>(""); // User's wallet address
  const [loggedIn, setLoggedIn] = useState(false); // Login state
  const web3auth = useRef<Web3Auth | null>(null); // Ref for Web3Auth instance
  const [isInitialized, setIsInitialized] = useState(false); // Web3Auth initialization status

  // Form inputs for recipient and amount
  const [recipientAddress, setRecipientAddress] = useState("");
  const [amount, setAmount] = useState<number>(0);

  const isValidAddress = (address: string): boolean => ethers.isAddress(address);

  const handleAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = parseFloat(e.target.value);
    if (isNaN(value) || value <= 0) {
      console.error("Invalid input for amount:", e.target.value);
      return;
    }
    setAmount(value);
  };

  // Function to fetch and set the user's wallet address
  const fetchWalletAddress = async () => {
    if (provider) {
      const signer = new ethers.BrowserProvider(provider).getSigner();
      const address = await (await signer).getAddress();
      setWalletAddress(address);
    }
  };

  // Function to fetch and set the user's USDC balance
  const fetchUsdcBalance = async () => {
    if (provider) {
      const signer = new ethers.BrowserProvider(provider).getSigner();
      const usdcContract = new ethers.Contract(
        usdcContractAddress,
        ["function balanceOf(address owner) view returns (uint256)"],
        await signer
      );
      const balance = await usdcContract.balanceOf(await (await signer).getAddress());
      setUsdcBalance(formatUnits(balance, 6)); // Assuming USDC has 6 decimals
    }
  };

  // Initialization of Web3Auth
  useEffect(() => {
    const init = async () => {
      try {
        const web3AuthOptions: Web3AuthOptions = {
          clientId,
          web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
          privateKeyProvider,
          accountAbstractionProvider,
        };

        // Initialize Web3Auth
        web3auth.current = new Web3Auth(web3AuthOptions);
        await web3auth.current.initModal();

        // Set initial state
        setIsInitialized(true);
        setProvider(web3auth.current.provider);
        if (web3auth.current.connected) {
          setLoggedIn(true);
          await fetchWalletAddress();
          await fetchUsdcBalance();
        }
      } catch (error) {
        console.error("Error initializing Web3Auth:", error);
      }
    };

    init(); // Call the initialization function
  }, []);

   // Refetch balance when logged in state changes
  useEffect(() => {
    if (loggedIn) {
      fetchWalletAddress();
      fetchUsdcBalance();

    }
  }, [loggedIn]);

   // Function to handle USDC transfer
   const sendUSDC = async () => {
    try {
      // Ensure the provider is initialized
      if (!provider) {
        throw new Error("Provider is not initialized. Please log in first.");
      }

      // Ensure the Web3Auth instance is ready
      if (!isInitialized) {
        throw new Error("Web3Auth is not fully initialized. Please wait and try again.");
      }

      // Validate recipient address
      if (!isValidAddress(recipientAddress)) {
        throw new Error("Invalid recipient address.");
      }

      // Validate amount
      if (!amount || isNaN(amount)) {
        throw new Error("Invalid amount entered.");
      }

      const amountBigInt = parseUnits(amount.toString(), 6);
      if (!amountBigInt || amountBigInt <= 0n) {
        throw new Error("Invalid transfer amount.");
      }

      console.log("Initializing signer...");
      const signer = new ethers.BrowserProvider(provider).getSigner();

      console.log("Connecting to USDC contract...");
      const usdcContract = new ethers.Contract(
        usdcContractAddress,
        [
          "function balanceOf(address owner) view returns (uint256)",
          "function transfer(address to, uint256 amount) returns (bool)",
        ],
        await signer
      );

      console.log("Executing transfer...");
      const transferTx = await usdcContract.transfer(recipientAddress, amountBigInt, {
        gasLimit: 100000n, // Adjust as needed
      });

      console.log("Transaction sent, hash:", transferTx.hash);
      await transferTx.wait(); // Wait for transaction confirmation
      console.log("Transfer confirmed.");

      // Refresh balance after transfer
      await fetchUsdcBalance();
      console.log("Balance updated. Transfer successful!");
    } catch (error: any) {
      console.error("Transaction failed:", error.message || error);
    }
  };

  // Function to log in the user
  const login = async () => {
    if (web3auth.current) {
      const web3authProvider = await web3auth.current.connect();
      setProvider(web3authProvider);
      setLoggedIn(true);
      await fetchWalletAddress();
      await fetchUsdcBalance();
    }
  };

  // Function to log out the user
  const logout = async () => {
    if (web3auth.current) {
      await web3auth.current.logout();
      setProvider(null);
      setLoggedIn(false);
      setWalletAddress("");
    }
  };

  // Views for logged-in and unlogged-in states
const loggedInView = (
  <Box sx={{ display: "flex", justifyContent: "center", mt: 3 }}>
    <Paper
      elevation={3}
      sx={{
        width: "100%",
        maxWidth: 450,
        borderRadius: 4,
        p: 3,
        textAlign: "center",
        backgroundColor: "#f5f5f5",
      }}
    >
      {/* Wallet overview */}
      <Typography variant="h5" gutterBottom>
        Wallet Overview
      </Typography>

      {/* Wallet address display */}
      <Box sx={{ mt: 2, mb: 2 }}>
        <Typography variant="subtitle2" color="textSecondary">
          Wallet Address:
        </Typography>
        <Typography
          variant="body2"
          sx={{
            wordBreak: "break-word",
            overflowWrap: "anywhere",
            fontWeight: "bold",
            mt: 1,
            backgroundColor: "#e0e0e0",
            p: 1,
            borderRadius: 1,
          }}
        >
          {walletAddress}
        </Typography>
        <Button
          onClick={() => {
            navigator.clipboard.writeText(walletAddress);
            alert("Wallet address copied!");
          }}
          variant="outlined"
          color="primary"
          sx={{ mt: 1 }}
        >
          Copy Address
        </Button>
      </Box>

      {/* USDC balance display */}
      <Box sx={{ mt: 3 }}>
        <Typography variant="subtitle2" color="textSecondary">
          USDC Balance:
        </Typography>
        <Typography variant="h6" color="primary" sx={{ fontWeight: "bold", mt: 1 }}>
          {usdcBalance ? usdcBalance : "Loading..."}
        </Typography>
      </Box>

      {/* Form for transferring USDC */}
      <Box sx={{ mt: 3, mb: 2 }}>
        <Typography variant="subtitle2" color="textSecondary">
          USDC Transfer
        </Typography>
        <TextField
          label="Recipient Address"
          variant="outlined"
          fullWidth
          value={recipientAddress}
          onChange={(e) => setRecipientAddress(e.target.value)}
          sx={{ mt: 1, mb: 2 }}
          helperText="Enter the recipient's wallet address"
        />
        <TextField
          label="Amount (USDC)"
          type="number"
          variant="outlined"
          fullWidth
          value={amount}
          onChange={handleAmountChange}
          sx={{ mb: 2 }}
          helperText="Amount to send in USDC"
        />
        {/* Transfer Button */}
        <Button
          onClick={sendUSDC}
          disabled={
            !amount ||
            amount > parseFloat(usdcBalance) ||
            !isValidAddress(recipientAddress)
          }
          variant="contained"
          color="secondary"
          fullWidth
          sx={{ mt: 2 }}
        >
          Send USDC
        </Button>
        {!amount || amount > parseFloat(usdcBalance) ? (
          <Typography
            variant="body2"
            color="error"
            sx={{ mt: 1, textAlign: "center" }}
          >
            Insufficient Balance or Invalid Inputs
          </Typography>
        ) : null}
      </Box>

      <Box sx={{ display: "flex", justifyContent: "center", mt: 3 }}>
        <Button onClick={logout} variant="contained" color="error">
          Logout
        </Button>
      </Box>
    </Paper>
  </Box>
);
  const unloggedInView = (
    <button onClick={login} className="card">Login</button>
  );

  return (
    <div className="container">
      <h1 className="title">
        <a target="_blank" href="https://web3auth.io/docs/sdk/pnp/web/modal" rel="noreferrer">
          PennyFundMe{" "}
        </a>
        & NextJS Quick Start
      </h1>
      {!isInitialized ? (
      <p>Loading Web3Auth...</p> // Display loading message if still initializing
    ) : (
      <div className="grid">{loggedIn ? loggedInView : unloggedInView}</div>
    )}
    </div>
  );
}

export default App;

Hi @Ayush.
A reminder about my issue.

I went through the code, here’s what I understood. You are using the AA Provider, creating an AA account for the user. You want to transfer the USDC from the AA account.

The approach you are using is for the EOA wallets, and not for the AA account. You are submitting a EOA transaction through RPC, for AA account, you need to submit a UserOp using the bundler.

Try this

// Use the same accountAbstractionProvider we you created earlier.
// Don't use the web3auth.provider as it will provide the Proxy.
const bundlerClient = accountAbstractionProvider.bundlerClient!;
const smartAccount = accountAbstractionProvider.smartAccount!;

// USDC address on Ethereum Sepolia
const usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";

// 10 USDC in WEI format. Since USDC has 6 decimals, 10 * 10^6
const transferAmount = 10000000n;

const userOpHash = await bundlerClient.sendUserOperation({
  account: smartAccount,
  calls: [
    // Transfer USDC on Sepolia chain
    {
      to: usdcAddress,
      abi: parseAbi(["function transfer(address to, uint256 amount)"]),
      functionName: "transfer",
      args: ["TO_ADDRESS", transferAmount],
    },
  ],
});

// Retrieve user operation receipt
const receipt = await bundlerClient.waitForUserOperationReceipt({
  hash: userOpHash,
});

const transactionHash = receipt.receipt.transactionHash;

The other way would be to encode the transfer data using the Ethers, and then send a transaction using the signer, where “to” is USDC Address, “data” is the encoded data for the transfer, and “value” is “0x” as no ETH transfer is involved.

Hi @Ayush

Thank you, very grateful for your assistance, issue resolved.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.