ethers-io / ethers.js

Complete Ethereum library and wallet implementation in JavaScript.
https://ethers.org/
MIT License
7.97k stars 1.85k forks source link

Support for Switching Chains with EIP-3326 #2842

Open qstearns opened 2 years ago

qstearns commented 2 years ago

Describe the Feature

Hello,

Thanks for the great work on Ethers. I have a question on introducing the ability to switch chains. The docs allude to the fact that this may not be a best practice, but since those docs were written EIP-3326 has come into play, which seems to suggest that switching networks is a reasonable practice. Furthermore, it seems like an increasingly common pattern for applications to support multiple chains where seamless switching is a bigger requirement for a positive user experience.

Is there any chance that ethers would consider incorporating the switch network functionality described in eip-3326?

Code Example

let result = await provider.switchNetwork({ chainId: 137, rpc-url: "...", ... })
ricmoo commented 2 years ago

I had not seen that EIP before; how widely adopted is it? Does it for example work in MM?

The ethers providers do support a changing network, by passing in the name "any" as the network name, but doing so means there are a lot of edge cases you must consider.

What might make sense for a future version of ethers is a switchNetwork method which returns the new Provider, which means that method can also handle pausing all events and cancelling all inflight calls.

It probably won’t accept an rpc url as input, but might make sense for the new v6 support for a provider having access to other providers for L2/L1 interoperability using a plugin.

I’ll think more about it. I’m about to dive into the v6 provider again today.

qstearns commented 2 years ago

Metamask does indeed support EIP-3326. Afraid to say I don't know how broad ecosystem support is, but would be happy to report back here if I find differences as I integrate with other providers.

Definitely understand that this isn't something that will be released tonight, and I appreciate the consideration.

Could you provide an example of where support of the API through which changing the network is possible? Into what call would I pass in "any" as the network name? I'll likely avoid this in order to dodge the edge cases, but would be curious to see how the interface works

ricmoo commented 2 years ago

Quite outdated, but here is what I currently have: https://docs.ethers.io/v5/concepts/best-practices/#best-practices--network-changes

At the time, the only people who ever changed networks were developers switching between testnets and mainnet. But now it's far more common with all the alternate networks with a mainnet. :)

qstearns commented 2 years ago

Thanks ricmoo, that's super helpful. I was playing with this example earlier and couldn't get to work.

Now I see it's clearly because I missed the inclusion of "any". There are certainly a lot more networks in the mix nowadays -- definitely makes ethers' job a bit more complicated!

akshatmittal commented 2 years ago

This definitely an important one, although most provider management libraries like wagmi, web3-react, onboard, etc already support it. Ethers is also able to hot-switch between networks as necessary. We also have a custom switcher implementation based on ethers, which basically uses "any" network.

On a side note, do we have a public tracker for v6? Saw it being mentioned at a few places but no clue on where the code/tracker is. @ricmoo

allenchuang commented 2 years ago

hello! i'm using web3Modal in conjunction with ethers. I'm trying to handle network change events by following the best practice listed here: https://docs.ethers.io/v5/concepts/best-practices/

However, I noticed the cb for web3Provider.on('network', (newNetwork, oldNetwork) => { ... } ) event is never called! What's weird though, is my `chainChanged' event is called just fine.

const provider = await web3Modal.connect();
const web3Provider = new ethers.providers.Web3Provider(provider, "any");

// EIP-1193 events
  useEffect(() => {
    if (provider?.on) {
      const handleAccountsChanged = (accounts: string[]) => {
        // toast.info("Changed Web3 Account");
        removeAuthTokens();

        dispatch({
          type: "SET_ADDRESS",
          address: accounts[0],
        } as Web3Action);
      };

      // https://docs.ethers.io/v5/concepts/best-practices/#best-practices--network-changes
      const handleChainChanged = (_hexChainId: string) => {
        removeAuthTokens();

        if (typeof window !== "undefined") {
          console.log("switched to chain...", _hexChainId);

          // window.location.reload();
        } else {
          console.log("window is undefined");
        }
      };

      const handleDisconnect = (error: { code: number; message: string }) => {
        // eslint-disable-next-line no-console
        console.log("disconnect", error);
        removeAuthTokens();

        disconnect();
      };

      provider.on("accountsChanged", handleAccountsChanged);
      provider.on("chainChanged", handleChainChanged);
      provider.on("disconnect", handleDisconnect);

      // Subscription Cleanup
      return () => {
        if (provider.removeListener) {
          provider.removeListener("accountsChanged", handleAccountsChanged);
          provider.removeListener("chainChanged", handleChainChanged);
          provider.removeListener("disconnect", handleDisconnect);
        }
      };
    }

    if (web3Provider?.on) {
      const handleNetworkChanged = (newNetwork, oldNetwork) => {
        // When a Provider makes its initial connection, it emits a "network"
        // event with a null oldNetwork along with the newNetwork. So, if the
        // oldNetwork exists, it represents a changing network
        console.log({ oldNetwork, newNetwork });
        if (oldNetwork) {
          window.location.reload();
        }
      };

      web3Provider.on("network", handleNetworkChanged);

      return () => {
        if (web3Provider.removeListener) {
          web3Provider.removeListener("network", handleNetworkChanged);
        }
      };
    }
  }, [provider, web3Provider, disconnect, toast, removeAuthTokens]);
0xNeshi commented 1 year ago

I had not seen that EIP before; how widely adopted is it? Does it for example work in MM?

The ethers providers do support a changing network, by passing in the name "any" as the network name, but doing so means there are a lot of edge cases you must consider.

What might these "edge cases" be? Does the page still need to be reloaded even with the more recent ethers version?