Search code examples
javascriptreactjsethereumweb3jsmetamask

Managed to connect with Metamask in my website but I can't show the balance


I managed to connect with MetaMask via my application using the following code:

import React, { useEffect, useState } from "react";
import Web3 from "web3";
import styles from "./MetamaskAuthStyle.css";

function isMobileDevice() {
  return "ontouchstart" in window || "onmsgesturechange" in window;
}

async function connect(onConnected) {
  if (!window.ethereum) {
    alert("Get MetaMask!");
    return;
  }

  const accounts = await window.ethereum.request({
    method: "eth_requestAccounts",
  });

  onConnected(accounts[0]);
}

async function checkIfWalletIsConnected(onConnected) {
  if (window.ethereum) {
    const accounts = await window.ethereum.request({
      method: "eth_accounts",
    });

    if (accounts.length > 0) {
      const account = accounts[0];
      onConnected(account);
      return;
    }

    if (isMobileDevice()) {
      await connect(onConnected);
    }
  }
}

// async function getBalance(userAddress) {
//   console.log(web3.eth.getBalance(userAddress));
//   return web3.eth.getBalance(userAddress);
// }

export default function MetaMaskAuth({ onAddressChanged }) {
  const [userAddress, setUserAddress] = useState("");
  let web3: Web3 = new Web3();

  useEffect(() => {
    checkIfWalletIsConnected(setUserAddress);
  }, []);

  useEffect(() => {
    console.log(web3.eth.getBalance(userAddress));
    onAddressChanged(userAddress);
  }, [userAddress]);

  return userAddress ? (
    <div>
      Connected with <Address userAddress={userAddress} />
      <p>Balance: </p>
    </div>
  ) : (
    <Connect setUserAddress={setUserAddress} />
  );
}

function Connect({ setUserAddress }) {
  if (isMobileDevice()) {
    const dappUrl = "metamask-auth.ilamanov.repl.co"; // TODO enter your dapp URL. For example: https://uniswap.exchange. (don't enter the "https://")
    const metamaskAppDeepLink = "https://metamask.app.link/dapp/" + dappUrl;
    return (
      <a href={metamaskAppDeepLink}>
        <button className={styles.button}>Connect to MetaMask</button>
      </a>
    );
  }

  return (
    <button className={styles.button} onClick={() => connect(setUserAddress)}>
      Connect to MetaMask
    </button>
  );
}

function Address({ userAddress }) {
  return (
    <span className={styles.address}>
      {userAddress.substring(0, 5)}…
      {userAddress.substring(userAddress.length - 4)}
    </span>
  );
}

I'm pretty new in this domain and I want to find out how can I display the balance of the logged in user and display a disconnect button, so the user can log off. I already tried making a getBalance function but the I got the following error:

Error: Provided address is invalid, the capitalization checksum test failed, or it's an indirect IBAN address which can't be converted


Solution

  • Here is an approach on how to connect your React app to Metamask via Web3.js. The main idea is to use React hooks to wrap around the intended web3.js functions. The following shows both (1) how to get the current account's balance and (2) a balance of any specified account.

    First lets create a custom React hook useWeb3.js in a separate file and put all Metamask/web3 connection logic there. This hook is as follows:

    import { useState } from 'react';
    import Web3 from 'web3';
    
    function useWeb3(setIsLoading, setErrorMessage) {
      // web3 instance
      const [provider, setProvider] = useState(null);
      // active account
      const [account, setAccount] = useState('');
      // connect this function to a button click event
      const connect = async () => {
        try {
          setIsLoading(true);
          setErrorMessage('');
          // ensure Metamask is installed
          if (!window.ethereum) throw new Error('You should enable Metamask');
          // show Metamask prompt
          await window.ethereum.request({ method: 'eth_requestAccounts' });
          // connect Metamask to web3.js and get a web3 provider instance
          const web3 = new Web3(window.ethereum);
          setProvider(web3);
          // refresh account on change
          window.ethereum.on('accountsChanged', (accounts) =>
            setAccount(accounts[0]),
          );
          // retrieve Metamask accounts from web3
          const accounts = await web3.eth.getAccounts();
          setAccount(accounts[0]);
        } catch (error) {
          setErrorMessage(error.message);
        } finally {
          setIsLoading(false);
        }
      };
      // connect this function to a disconnect button
      const disconnect = () => {
        setProvider(null);
        setAccount('');
      };
      return { connect, disconnect, provider, account };
    }
    
    export default useWeb3;
    
    

    Note that it's convenient to move metamask/web3 connection code into its own custom hook, for reuse in multiple components or parts of the app.

    To dynamically observe balance information, we can create a useBalance() hook. Here is an example implementation which consumes the above useWeb3() hook:

    import { useEffect, useState } from 'react';
    
    function useBalance(web3, account, setLoading, setErrorMessage) {
      const [balance, setBalance] = useState('?');
      useEffect(() => {
        if (web3 && account) {
          (async () => {
            try {
              setLoading(true);
              setErrorMessage('');
              // Will convert an upper or lowercase Ethereum address to a checksum address.
              // https://web3js.readthedocs.io/en/v1.2.11/web3-utils.html#tochecksumaddress
              const correctedAccount = web3.utils.toChecksumAddress(account);
              const balanceInWei = await web3.eth.getBalance(correctedAccount);
              setBalance(
                // convert balance from wei to ether
                Number(web3.utils.fromWei(balanceInWei, 'ether')).toFixed(2),
              );
            } catch (error) {
              setErrorMessage(error.message);
              setBalance('?');
            } finally {
              setLoading(false);
            }
          })();
        }
      }, [web3, account, setLoading, setErrorMessage]);
      return balance;
    }
    
    export default useBalance;
    
    

    Then we can connect to a real blockchain to see if everything works. To add connections to the RSK network, use the configuration in Metamask as described here.

    Now in App.js we can put all the hooks together to retrieve balances.

    Bonus example

    This is another component which contains a text field where a user can paste any address from the RSK explorer and see its balance. It also makes use of the above useWeb3() hook.

    import { useState } from 'react';
    import useWeb3 from './hooks/useWeb3';
    import useBalance from './hooks/useBalance';
    import './App.css';
    
    function App() {
      // loading status
      const [isLoading, setIsLoading] = useState(false);
      // error messages
      const [errorMessage, setErrorMessage] = useState('');
      // get active account and balance data from useWeb3 hook
      const {
        connect,
        disconnect,
        provider,
        account: activeAccount,
      } = useWeb3(setIsLoading, setErrorMessage);
      // get active account balance from useBalance hook
      const activeBalance = useBalance(
        provider,
        activeAccount,
        setIsLoading,
        setErrorMessage,
      );
    
      // random non-empty account from RSK explorer https://explorer.rsk.co/
      const [customAcount, setCustomAccount] = useState(
        '0xC2a41f76CaCFa933c3496977f2160944EF8c2de3',
      );
      // get balance of the custom account
      const customBalance = useBalance(
        provider,
        customAcount,
        setIsLoading,
        setErrorMessage,
      );
    
      return (
        <div className="App">
          {/* instantiate web3 only after a user clicks the button */}
          {/* avoid doing it automatically */}
          {!provider ? (
            <button onClick={connect}>Connect to MetaMask</button>
          ) : (
            <>
              <p>Connected with {activeAccount}</p>
              <p>My balance: {activeBalance} RBTC</p>
    
              {/* let a user enter any address and see its balance */}
    
              <p>Check RSK account:</p>
              <input
                type="text"
                value={customAcount}
                onChange={(e) => setCustomAccount(e.target.value)}
                style={{ width: '25rem' }}
              />
              <p>
                <a href={`https://explorer.rsk.co/address/${customAcount}`}>
                  RSK account
                </a>
                {' balance:'}
                {customBalance} RBTC
              </p>
              <button onClick={disconnect}>Disconnect</button>
            </>
          )}
          {/* show loading and error statuses */}
          {isLoading && <p>Loading...</p>}
          {errorMessage && <p>{errorMessage}</p>}
        </div>
      );
    }
    
    export default App;
    
    
    Connected with 0x1926617E081D3a670b3553FB0ba75180Bc01b215
    
    My balance: 0.00 RBTC
    
    Check RSK account:
    
    0xC2a41f76CaCFa933c3496977f2160944EF8c2de3
    RSK account balance:10.53 RBTC
    
    [Disconnect]
    

    If you would like to try out a working example, check out demo-code-snippets/web3-react-show-balance.