Argument of type '(accounts: string[]) => void' is not assignable to parameter of type '(...args: unknown[]) => void'

37 views Asked by At

I'm new to web3 development and am getting the following error in 2 places.

Error 1)

Argument of type '(accounts: string[]) => void' is not assignable to parameter of type '(...args: unknown[]) => void'.
  Types of parameters 'accounts' and 'args' are incompatible.
    Type 'unknown' is not assignable to type 'string[]'.

Error 2)

Argument of type '(network: string) => void' is not assignable to parameter of type '(...args: unknown[]) => void'.
  Types of parameters 'network' and 'args' are incompatible.
    Type 'unknown' is not assignable to type 'string'.ts(2345)

Code

import { BrowserProvider, ethers, JsonRpcSigner} from "ethers";
import { useToast } from "@chakra-ui/react";
import { useCallback, useEffect, useState } from "react";
import { MetaMaskInpageProvider } from "@metamask/providers";

declare global {
  interface Window{
    ethereum?:MetaMaskInpageProvider
  }
}


export interface IWeb3State {
  address: string | null;
  currentChain: number | null; 
  signer: JsonRpcSigner | null;
  provider: BrowserProvider | null;
  isAuthenticated: boolean;
}

const useWeb3Provider = () => {
  const initialWeb3State = {
    address: null,
    currentChain: null,
    signer: null,
    provider: null,
    isAuthenticated: false,
  };

  const toast = useToast();
  const [state, setState] = useState<IWeb3State>(initialWeb3State);

  const connectWallet = useCallback(async () => {
    if (state.isAuthenticated) return;

    try {
      const { ethereum } = window;

      if (!ethereum) {
        return toast({
          status: "error",
          position: "top-right",
          title: "Error",
          description: "No ethereum wallet found",
        });
      }
      const provider = new ethers.BrowserProvider(ethereum);

      const accounts: string[] = await provider.send("eth_requestAccounts", []) as string[];

      if (accounts.length > 0) {
        const signer = await provider.getSigner();
        const chain = Number(await (await provider.getNetwork()).chainId);
        //once we have the wallet, are we exporting the variables "provider" & accounts outside this try?
        setState({
          ...state,
          address: accounts[0],
          signer,
          currentChain: chain,
          provider,
          isAuthenticated: true,
        });

        localStorage.setItem("isAuthenticated", "true");
      }
    } catch {}
  }, [state, toast]);

  const disconnect = () => {
    setState(initialWeb3State);
    localStorage.removeItem("isAuthenticated");
  };

  useEffect(() => {
    if (window == null) return;

    if (localStorage.hasOwnProperty("isAuthenticated")) {
      connectWallet();
    }
  }, [connectWallet, state.isAuthenticated]);

  useEffect(() => {
    if (typeof window.ethereum === "undefined") return;

    window.ethereum.on("accountsChanged", (accounts: string[]) => {
      setState({ ...state, address: accounts[0] });
    });

    window.ethereum.on("networkChanged", (network: string) => {
      setState({ ...state, currentChain: network });
    });

    return () => {
      window.ethereum?.removeAllListeners();
    };
  }, [state]);

  return {
    connectWallet,
    disconnect,
    state,
  };
};

export default useWeb3Provider;

If it helps, I'm following this tutorial and have been trouble shooting for the past 3 days. Insert crying emoji

I'm not particularly sure how to even ask this question so if any further clarification is needed, just holler!

1

There are 1 answers

0
SakoBu On BEST ANSWER

This doesn't have much to do with web3. It's more of a typescript question. See fixes below with comments:

import { BrowserProvider, ethers, JsonRpcSigner } from "ethers";
import { useToast } from "@chakra-ui/react";
import { useCallback, useEffect, useState } from "react";
import { MetaMaskInpageProvider } from "@metamask/providers";

declare global {
  interface Window {
    ethereum?: MetaMaskInpageProvider;
  }
}

export interface IWeb3State {
  address: unknown; // Fix: Change from string | null; ❌
  currentChain: unknown; // Fix: Change from number | null; ❌
  signer: JsonRpcSigner | null;
  provider: BrowserProvider | null;
  isAuthenticated: boolean;
}

const useWeb3Provider = () => {
  const initialWeb3State = {
    address: null,
    currentChain: null,
    signer: null,
    provider: null,
    isAuthenticated: false,
  };

  const toast = useToast();
  const [state, setState] = useState<IWeb3State>(initialWeb3State);

  const connectWallet = useCallback(async () => {
    if (state.isAuthenticated) return;

    try {
      const { ethereum } = window;

      if (!ethereum) {
        return toast({
          status: "error",
          position: "top-right",
          title: "Error",
          description: "No ethereum wallet found",
        });
      }
      const provider = new ethers.BrowserProvider(ethereum);

      const accounts: string[] = (await provider.send("eth_requestAccounts", [])) as string[];

      if (accounts.length > 0) {
        const signer = await provider.getSigner();
           // unnecessary await here ❌
    const chain = Number((await provider.getNetwork()).chainId);
        //once we have the wallet, are we exporting the variables "provider" & accounts outside this try?
        setState({
          ...state,
          address: accounts[0],
          signer,
          currentChain: chain,
          provider,
          isAuthenticated: true,
        });

        localStorage.setItem("isAuthenticated", "true");
      }
      // Empty block statement is also an issue ❌
    } catch (e) {
      console.error(e);
    }
  }, [state, toast]);

  const disconnect = () => {
    setState(initialWeb3State);
    localStorage.removeItem("isAuthenticated");
  };

  useEffect(() => {
    if (window == null) return;
    // Do not access Object.prototype method 'hasOwnProperty' from target object. (Fix Below) ❌
    if (Object.prototype.hasOwnProperty.call(localStorage, "isAuthenticated")) {
      connectWallet();
    }
  }, [connectWallet, state.isAuthenticated]);

  useEffect(() => {
    if (typeof window.ethereum === "undefined") return;

    // '(...args: unknown[]) => void'. (Fix Below and in IWeb3State above) - literaaly telling you the expected type in the error message ❌
    window.ethereum.on("accountsChanged", (...accounts: unknown[]) => {
      setState({ ...state, address: accounts[0] });
    });

    // '(...args: unknown[]) => void'. (Fix Below and in IWeb3State above) - literaaly telling you the expected type in the error message ❌
    window.ethereum.on("networkChanged", (network: unknown) => {
      setState({ ...state, currentChain: network });
    });

    return () => {
      window.ethereum?.removeAllListeners();
    };
  }, [state]);

  return {
    connectWallet,
    disconnect,
    state,
  };
};

export default useWeb3Provider;

An alternative fix would be to use typescript generics:

Here:

// Alternative fix with the use of typescript generics
export interface IWeb3State<T> {
  address: T;
  currentChain: T;
  signer: JsonRpcSigner | null;
  provider: BrowserProvider | null;
  isAuthenticated: boolean;
}

And here:

 // pass unknown here as a type argument to IWeb3State
  const [state, setState] = useState<IWeb3State<unknown>>(initialWeb3State);