Build Error with permissionless and viem in Web3Auth Integration

We ran into this issue few days ago and have not been able to resolve it. just implementing some minor updates on our website and unable to redeploy.

SDK Version (package.json):

@web3auth/account-abstraction-provider”: “^9.7.0”,

@web3auth/base”: “^9.7.0”,

@web3auth/ethereum-provider”: “^9.7.0”,

@web3auth/modal”: “^9.7.0”,

“viem”: “2.28.0”

Platform:

  • Framework: Next.js 15.0.3
  • Node.js Version: 22.x (on Vercel) and 20.18.0 (locally)
  • Operating System: Windows 10 (local development)

Browser Console Screenshots: The error occurs during the build process, so there are no browser console logs. Below is the build error message:

./node_modules/permissionless/accounts/biconomy/toBiconomySmartAccount.ts:217:5
Type error: Type '{ client: Client<Transport, Chain | undefined, { address: `0x${string}`; nonceManager?: NonceManager | undefined; sign?: ((parameters: { hash: `0x${string}`; }) => Promise<...>) | undefined; ... 6 more ...; type: "local"; } | JsonRpcAccount | undefined>; ... 17 more ...; type: "smart"; } | { ...; }' is not assignable to type 'ToBiconomySmartAccountReturnType'.
  Type '{ client: Client<Transport, Chain | undefined, { address: `0x${string}`; nonceManager?: NonceManager | undefined; sign?: ((parameters: { hash: `0x${string}`; }) => Promise<...>) | undefined; ... 6 more ...; type: "local"; } | JsonRpcAccount | undefined>; ... 17 more ...; type: "smart"; }' is not assignable to type 'ToBiconomySmartAccountReturnType'.
    Type '{ client: import("/vercel/path0/node_modules/viem/_types/clients/createClient").Client<import("/vercel/path0/node_modules/viem/_types/clients/transports/createTransport").Transport, import("/vercel/path0/node_modules/viem/_types/types/chain").Chain | undefined, { ...; } | ... 1 more ... | undefined>; ... 17 more ......' is not assignable to type '{ client: import("/vercel/path0/node_modules/viem/_types/clients/createClient").Client<import("/vercel/path0/node_modules/viem/_types/clients/transports/createTransport").Transport, import("/vercel/path0/node_modules/viem/_types/types/chain").Chain | undefined, { ...; } | ... 1 more ... | undefined>; ... 17 more ......'. Two different types with this name exist, but they are unrelated.
      The types of 'entryPoint.abi' are incompatible between these types.
        Type 'Abi' is not assignable to type 'readonly [{ readonly inputs: readonly [{ readonly name: "preOpGas"; readonly type: "uint256"; }, { readonly name: "paid"; readonly type: "uint256"; }, { readonly name: "validAfter"; readonly type: "uint48"; }, { ...; }, { ...; }, { ...; }]; readonly name: "ExecutionResult"; readonly type: "error"; }, ... 35 more ......'.
          Target requires 37 element(s) but source may have fewer.

Web3Auth Initialization and Login Code Snippet: Here is the relevant code snippet for Web3Auth initialization and login:

"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/80002/rpc?apikey=${pimlicoAPIKey}`, // Bundler API URL
    },
    paymasterConfig: {
      url: `https://api.pimlico.io/v2/80002/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 provider and accountAbstractionProvider are initialized
      if (!provider || !accountAbstractionProvider) {
        console.error("Provider or Account Abstraction Provider is not initialized.");
        return;
      }
  
      if (!isValidAddress(recipientAddress)) {
        console.error("Invalid recipient address:", recipientAddress);
        return;
      }
  
      if (!amount || isNaN(amount)) {
        console.error("Invalid amount entered:", amount);
        return;
      }
  
      console.log("Preparing to send USDC using Account Abstraction...");
  
      // Convert the amount to BigInt with USDC's decimals (6 decimals)
      const transferAmount = parseUnits(amount.toString(), 6);
  
      // Get the bundler client and smart account from the AA provider
      const bundlerClient = accountAbstractionProvider.bundlerClient!;
      const smartAccount = accountAbstractionProvider.smartAccount!;
  
      // Create and submit the User Operation
      const abi = new ethers.Interface([
        "function transfer(address to, uint256 amount)"
      ]);
      
      const userOpHash = await bundlerClient.sendUserOperation({
        account: smartAccount,
        calls: [
          {
            to: usdcContractAddress, // USDC contract address
            abi: abi.fragments, // Use ABI fragments from the Interface instance
            functionName: "transfer",
            args: [recipientAddress, transferAmount],
          },
        ],
      });
  
      console.log("User operation submitted. Hash:", userOpHash);
  
      // Wait for User Operation receipt
      const receipt = await bundlerClient.waitForUserOperationReceipt({
        hash: userOpHash,
      });
  
      console.log("Transaction confirmed. Hash:", receipt.receipt.transactionHash);
  
      // Update balance after successful transfer
      await fetchUsdcBalance();
      console.log("USDC transfer successful!");
  
    } catch (error: any) {
      console.error("Error during USDC transfer:", 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;

Code and Deployment:

Additional Notes:

  • The issue seems to be related to type mismatches between permissionless and viem. Specifically, the entryPoint.abi and client properties in the toSmartAccount function are causing type conflicts.
  • I have tried upgrading all related dependencies (permissionless, viem, @web3auth/*) to their latest versions, but the issue persists.
  • The error occurs during the build process on both local (Node.js 20.18.0) and Vercel (Node.js 22.x) environments.

Could you please assist in resolving this issue? Let me know if you need any additional details.

Thank you!

Hi Friday, thanks for sharing such a detailed report—this really helps narrow things down!

Issue Summary

You’re experiencing a TypeScript build error during your Next.js app’s build process.
The error involves a type mismatch between the permissionless and viem packages’ types, particularly with entryPoint.abi and client in the toBiconomySmartAccount function.

  • SDK Details: PnP Web SDK
  • Web3Auth Packages: ^9.4.0 (all core + AA)
  • Relevant Library Versions: viem@2.21.44, Next.js 15.0.3
  • Platforms: Node.js 20.18.0 (local), 22.x (Vercel)
  • Build Error:
    Type error relating to types from different (but identically named) versions of viem (see your log).

Diagnosis

This error is typically caused by version conflicts between dependencies—when two different parts of your project (or its dependencies) are pulling in two different versions (or instances) of the viem package, resulting in types that are identically named but technically distinct to TypeScript.

The key line in your error:

Two different types with this name exist, but they are unrelated.

This often happens when:

  1. permissionless and web3auth (or its providers) depend on different versions of viem
  2. You end up with multiple copies of viem in your node_modules tree.

Steps to Fix

  1. Deduplicate viem package

    Run this to see if you have multiple versions installed:

    npm ls viem
    # or for yarn
    yarn list viem
    
  2. Ensure a Single Version of viem

    You want only one viem version present. In package.json, ensure:

    "resolutions": {
      "viem": "2.21.44"
    }
    

    If you use yarn, it will “enforce” this. For npm, try npm-force-resolutions:

    • Add the “resolutions” field as above
    • Run:
      npx npm-force-resolutions
      npm install
      
  3. Clean and Reinstall

    Ensure caches are clear:

    rm -rf node_modules package-lock.json yarn.lock
    npm install # or yarn install
    
  4. Peer Dependencies Compatibility

    Make sure the permissionless, viem, and @web3auth/* packages you use are known to work together.

    Consider aligning all package versions to those from the latest @web3auth/account-abstraction-provider sample (if available). You can check the Web3Auth account abstraction quickstart.

  5. Check for Duplicate Imports

    In your code and dependencies, ensure you import viem and permissionless from only one source, and avoid direct default import/require cycles that reload modules.

  6. Temporarily Remove or Update permissionless

    You may try commenting out parts of your code that import and use permissionless (and related AA code) and see if the build succeeds, then introduce them one-by-one after ensuring the deduplication above.


Similar Issues


Next Steps

If the issue persists after deduplication:

  • Please run npm ls viem and paste the output here.
  • Mention if you use npm or yarn as your package manager.
  • If you see a specific sub-dependency asking for an older/newer version of viem, share package.json snippets or the dependency tree.

Let me know how it goes! If you’re still stuck, please upload your npm ls viem output so we can dig deeper.

Thank you for your patience and troubleshooting so far!

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.

Additionally, we would recommend you to join our biweekly Office Hours to get this resolved even faster. Web3Auth Developer Office Hours

We have actually tried steps 1,2,3,4 and 5 but the issue still persist.

  • Please run npm ls viem and paste the output here.
    ─┬ @web3auth/account-abstraction-provider@9.7.0
    │ ├─┬ permissionless@0.2.43
    │ │ └── viem@2.28.0 deduped
    │ └── viem@2.28.0 deduped
    ├─┬ @web3auth/ethereum-provider@9.7.0
    │ └── viem@2.28.0 deduped
    └── viem@2.28.0 overridden

Also we are using npm as package manager

I have the same problem trying to integrate the 9.7.0 AA provider with an angular 19 project. I’m hoping you’ve found a solution?

I’ve been trying to override versions of @web3auth/account-abstraction-provider, viem, and permissionless but I’ve had no luck.