Search code examples
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:

  • MetaMask Application: Connects perfectly when TrustWallet is not installed.
  • TrustWallet Application: After installation, interferes with MetaMask's functionality.

Environment:

  • Last version of TrustWallet for Chrome installed today.
  • Using web3.js library.

LocalStorage Behavior:

  • Without TrustWallet installed, localStorage is clean with only MetaMask data.
  • With TrustWallet, localStorage gets cluttered with entries from both wallets, including some unrelated keys.

Strange Behavior

  • When click to connect to Metamask, opens TrustWallet instead

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:

  • How can I ensure that MetaMask and TrustWallet do not interfere with each other's functionality? When click on MetaMask button open MetaMask, and when click on TrustWallet open TrustWallet

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