next.js13web3jsmetamasktrustwallet

Conflict Between MetaMask and TrustWallet Connections Using Web3 in Next.js


Hello Stack Overflow community,

I have developed two separate applications using Next.js and Web3, one for MetaMask and another for TrustWallet. However, I'm encountering a problem where attempting to connect with MetaMask results in the TrustWallet being triggered instead. Additionally, after installing TrustWallet, a lot of unnecessary data gets saved to my localStorage, which was not the case when TrustWallet was not installed.

Issue Details:

Environment:

LocalStorage Behavior:

Strange Behavior

MetaMask and TrustWallet Code Sample

import { useEffect, useState } from "react";
import Web3 from 'web3';
import { useLocalStorage } from "@/hooks/auth/useLocalStorage";

const TrustWalletOnlyApp = () => {
  const [account, setAccount] = useLocalStorage("eth_trustwallet_account", null);
  const [trustConnected, setTrustConnected] = useLocalStorage("eth_trustwallet_connected", false);
  const [walletInstalled, setWalletInstalled] = useState(false);
  const [balance, setBalance] = useState(null);
  const [isClient, setIsClient] = useState(false);

  console.log(walletInstalled, trustConnected, account, balance);

  useEffect(() => {
    setIsClient(true); // Marcar que estamos no cliente

    const checkWalletAvailability = async () => {
      if (typeof window.ethereum !== "undefined" && window.ethereum.isTrust) {
        setWalletInstalled(true);
        const accounts = await window.ethereum.request({ method: 'eth_accounts' });
        if (accounts.length > 0) {
          setAccount(accounts[0]);
          getBalance(accounts[0]);
          setTrustConnected(true);
        } else {
          setTrustConnected(false);
        }
      } else {
        setWalletInstalled(false);
        setTrustConnected(false);
      }
    };

    checkWalletAvailability();
  }, []);

  const onWalletDisconnect = () => {
    setAccount(null);
    setBalance(null);
    setTrustConnected(false);
    window.localStorage.removeItem("eth_trustwallet_account");
  };

  const onConnectClick = async () => {
    if (window.ethereum && window.ethereum.isTrust) {
      const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
      if (accounts.length > 0) {
        setAccount(accounts[0]);
        getBalance(accounts[0]);
        setTrustConnected(true);
        window.localStorage.setItem("eth_trustwallet_account", accounts[0]);
      }
    } else {
      console.log("Trust Wallet is not installed");
    }
  };

  const getBalance = async (account) => {
    const web3 = new Web3(window.ethereum);
    const balance = await web3.eth.getBalance(account);
    setBalance(web3.utils.fromWei(balance, 'ether'));
  };

  if (!isClient) {
    return null;
  }

  return (
    <div style={{ padding: 30 }}>
      <h1>Trust Wallet Connect Test App</h1>
      <div>
        {trustConnected ? (
          <div>
            <h3>Trust Wallet Account</h3>
            <div>Address: {account}</div>
            <div>Balance: {balance} ETH</div>
          </div>
        ) : (
          <div>
            <h3>No account connected</h3>
          </div>
        )}
      </div>
      <div style={{ background: "lightgray", padding: 30, marginTop: 10 }}>
        <button style={{ height: 30, width: 180, marginLeft: 10 }} onClick={onConnectClick}>
          Connect Trust Wallet
        </button>
      </div>
      <div>
        <button onClick={onWalletDisconnect}>Disconnect</button>
      </div>
    </div>
  );
};

export default TrustWalletOnlyApp;
import { useState, useEffect } from 'react';
import Web3 from 'web3';
import { useLocalStorage } from "@/hooks/auth/useLocalStorage";

const MetamaskOnlyApp = () => {
  const [account, setAccount] = useLocalStorage("eth_metamask_account", null);
  const [metamaskConnected, setMetamaskConnected] = useLocalStorage("eth_metamask_connected", false);
  
  const [metamaskInstalled, setMetamaskInstalled] = useState(false);
  const [balance, setBalance] = useState(null);
  const [isClient, setIsClient] = useState(false);
  
  console.log(metamaskInstalled, metamaskConnected, account, balance);

  useEffect(() => {
    setIsClient(true); // Marcar que estamos no cliente

    const checkMetamaskAvailability = async () => {
      if (typeof window !== "undefined" && window.ethereum && window.ethereum.isMetaMask) {
        setMetamaskInstalled(true);
        const accounts = await window.ethereum.request({ method: 'eth_accounts' });
        if (accounts.length > 0) {
          setAccount(accounts[0]);
          getBalance(accounts[0]);
          setMetamaskConnected(true);
        } else {
          setMetamaskConnected(false);
        }
      } else {
        setMetamaskInstalled(false);
        setMetamaskConnected(false);
      }
    };

    checkMetamaskAvailability();
  }, []);

  const onWalletDisconnect = () => {
    setAccount(null);
    setBalance(null);
    setMetamaskConnected(false);
    window.localStorage.removeItem("eth_metamask_account");
  };

  const onConnectClick = async () => {
    if (window.ethereum && window.ethereum.isMetaMask) {
      const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
      if (accounts.length > 0) {
        setAccount(accounts[0]);
        getBalance(accounts[0]);
        setMetamaskConnected(true);
        window.localStorage.setItem("eth_metamask_account", accounts[0]);
      }
    } else {
      console.log("MetaMask is not installed");
    }
  };

  const getBalance = async (account) => {
    const web3 = new Web3(window.ethereum);
    const balance = await web3.eth.getBalance(account);
    setBalance(web3.utils.fromWei(balance, 'ether'));
  };

  if (!isClient) {
    return null;
  }

  return (
    <div style={{ padding: 30 }}>
      <h1>MetaMask Connect Test App</h1>
      <div>
        {metamaskConnected ? (
          <div>
            <h3>MetaMask Account</h3>
            <div>Address: {account}</div>
            <div>Balance: {balance} ETH</div>
          </div>
        ) : (
          <div>
            <h3>No account connected</h3>
          </div>
        )}
      </div>
      <div style={{ background: "lightgray", padding: 30, marginTop: 10 }}>
        <button style={{ height: 30, width: 180, marginLeft: 10 }} onClick={onConnectClick}>
          Connect MetaMask
        </button>
      </div>
      <div>
        <button onClick={onWalletDisconnect}>Disconnect</button>
      </div>
    </div>
  );
};

export default MetamaskOnlyApp;

LocalStorage Output:

Before TrustWallet Installation:

{
  "eth_metamask_account": "0xb5c6a848...6e49475d11",
  "eth_metamask_connected": "true"
}

After TrustWallet Installation (same address for both wallets, the trustwallet address):

{
  "binance-http://localhost:3000": "{}",
  "eth_trustwallet_account": "0xb5c6a84.....A7cF6E49475d11",
  "eth_metamask_account": "0xb5c6a84......A7cF6E49475d11",
  "ethereum-http://localhost:3000": "{\"address\":\"0xb5c6a848f23d92d67e68b4fc1da7cf6e49475d11\",\"chainId\":\"0x1\",\"networkVersion\":1}",
  "eth_trustwallet_connected": "true",
  "trust:cache:timestamp": "{\"timestamp\":1717892710717}",
  "eth_metamask_connected": "true",
  "loglevel": "SILENT"
}

Questions:

Any insights or solutions would be greatly appreciated. Thank you!


Solution

  • Detecting multiple providers can be a problem because of conflicts.

    EIP-6963 aims to solve that.

    const [metaMaskProvider, setMetaMaskProvider] = useState(null)
    const [trustWalletProvider, setTrustWalletProvider] = useState(null)
    
    useEffect(() => {
      window.addEventListener("eip6963:announceProvider", (event) => {
        const provider = event.detail.provider
    
        if (provider.isMetaMask) {
          setMetaMaskProvider(provider)
        }
    
        if (provider.isTrust) {
          setTrustWalletProvider(provider)
        }
      })
    
      window.dispatchEvent(new Event("eip6963:requestProvider"))
    }, [])
    
    const isMetaMaskInstalled = !!metaMaskProvider
    const isTrustWalletInstalled = !!trustWalletProvider