web3Api.account.getNativeBalance not updating

I added the onAccountChanged listener to my useEffect and it correctly detects Metamask account changes, but the balance does not update when I call web3Api.account.getNativeBalance. I’m trying to listen for account changes and update the displayed balance.

Before trying Moralis, this all worked perfectly with ethers.js along the following lines, in useEffect:

function addWalletListener() {
      if (window.ethereum) {
        window.ethereum.on("accountsChanged", (accounts) => {
          if (accounts.length > 0) {
            setUserAccount(accounts[0]);
            getUserBalance(accounts[0]);
            setStatus("Got the account balance");
          } else {
            setUserAccount(null);
            setUserBalance(0);
            setIsConnected(false);
            setStatus("Metamask is installed but not connected");
          }
        });
      } else {
        setUserAccount(null);
        setUserBalance(0);
        setIsMetamask(false);
        setIsConnected(false);
        setStatus("Install Metamask for more features");
      }
    }

I’m trying to do the same thing in Moralis with this:

 const Web3Api = useMoralisWeb3Api();
  const {
    Moralis,
    authenticate,
    isAuthenticated,
    isAuthenticating,
    user,
    logout,
  } = useMoralis();
const [nativeBalance, setNativeBalance] = useState();

  useEffect(() => {
    async function getNativeBalance() {
      const { balance } = await Web3Api.account.getNativeBalance({
        chain: Moralis.chainId,
        account: Moralis.account,
      });
      console.log("Just got native balance", balance);
      setNativeBalance(balance);
    }

    if (isAuthenticated) {
      getNativeBalance();
    }

    const unsubAccountChanged = Moralis.onAccountChanged((account) => {
      console.log("New Account: ", account);
      console.log("Moralis Acct: ", Moralis.account);
      getNativeBalance();
    });
    const unsubChainChanged = Moralis.onChainChanged((chain) => {
      console.log(chain);
      console.log(Moralis.chainId);
      window.location.reload();
    });

    return () => {
      unsubAccountChanged();
      unsubChainChanged();
    };
  }, [
    isAuthenticated,
    Moralis.account,
    Moralis.chainId,
    Moralis,
    Web3Api.account,
  ]);

If you check the console, the account in Moralis is changing when I change the account in Metamask, but the nativeBalance is not changing.

Any ideas what I’m doing wrong here?

Is the balance logging correctly in getNativeBalance() after the chain is changed?

As an alternative, since you’re using react-moralis, you can use the useNativeBalance() hook.

const { data } = useNativeBalance();

data will update automatically when the chain is changed.

1 Like

@glad Yes, useNativeBalance is working and now I can remove this from useEffect altogether. But there is another strange behavior. When I refresh the screen (like what I want to do when the user changes to a different chainId) then this is the strange behavior:

isAuthenticated is still true
user.id is still the Moralis user
but now account is null and the useNativeBalance is null

What happens when you refresh the page, whether or not you are changing the chain, that user can be authenticated but the useMoralis returns a user but no account?

How do I find the account when I have a user, like after a refresh? Here is the code for the entire component. All I’m trying to do is constantly monitor which account the user has selected in Metamask and show the current account number and balance. This was easy using window.ethereum and ethers.js but I can’t seem to find the same way to do this very basic thing in Moralis. What am I missing:

import { useMoralis, useNativeBalance } from "react-moralis";
import { useEffect } from "react";

export default function Connect() {
  const {
    Moralis,
    authenticate,
    isAuthenticated,
    isAuthenticating,
    user,
    account,
    logout,
  } = useMoralis();

  const { data } = useNativeBalance();

  useEffect(() => {
    console.log("authenticated: ", isAuthenticated);
    console.log("user: ", user);
    console.log("account: ", account);
    console.log("balance: ", data.balance);

    const unsubAccountChanged = Moralis.onAccountChanged((account) => {
      console.log("New Account from onAccountChanged: ", account);
      console.log("Moralis.account: ", Moralis.account);
    });
    const unsubChainChanged = Moralis.onChainChanged((chain) => {
      console.log("New Chain from onChainChainged: ", chain);
      console.log("Moralis.chainId: ", Moralis.chainId);
      window.location.reload();
    });

    return () => {
      unsubAccountChanged();
      unsubChainChanged();
    };
  }, [Moralis, account, data.balance, isAuthenticated, user]);

  async function login() {
    if (!isAuthenticated) {
      await authenticate({ signingMessage: "Log in using Moralis" })
        .then(function (user) {
          console.log("logged in user:", user);
          console.log("account: ", Moralis.account);
        })
        .catch(function (error) {
          console.log(error);
        });
    }
  }

  const disconnect = async () => {
    await logout();
    console.log("logged out");
  };

  function balanceDisplay() {
    if (data.balance) {
      return parseFloat(Moralis.Units.FromWei(data.balance)).toFixed(6);
    } else {
      return "EMPTY";
    }
  }

  return (
    <div>
      {isAuthenticating && (
        <button className="btn btn-primary" disabled>
          Processing
        </button>
      )}
      {!isAuthenticating && !isAuthenticated && (
        <button className="btn btn-primary" onClick={login}>
          Connect
        </button>
      )}
      {!isAuthenticating && isAuthenticated && (
        <div>
          <button className="btn btn-primary mb-3" onClick={disconnect}>
            Disconnect
          </button>
          <p>user: {user.id}</p>
          <p>account: {Moralis.account}</p>
          <p>chainId: {Moralis.chainId} </p>
          <p>native Balance: {balanceDisplay()}</p>
        </div>
      )}
    </div>
  );
}

Here’s a little fix. No need to reload so far you’re using hooks.
Everything should sync appropriately.

import { useMoralis, useNativeBalance, useChain } from "react-moralis";
import { useEffect } from "react";

export default function Connect() {
  const {
    Moralis,
    authenticate,
    isAuthenticated,
    isAuthenticating,
    user,
    account,
    logout,
  } = useMoralis();
  const { chainId, chain } = useChain();

  const { data } = useNativeBalance();

  useEffect(() => {
    console.log("authenticated: ", isAuthenticated);
    console.log("user: ", user);
    console.log("account: ", account);
  }, [isAuthenticated, user]);

  useEffect(() => {
    console.log("New Account from onAccountChanged: ", account);
    console.log("Moralis.account: ", account);
  }, [account]);

  useEffect(() => {
    console.log("New Chain from onChainChainged: ", chain);
    console.log("Moralis.chainId: ", chainId);
  }, [chainId]);

  useEffect(() => {
    console.log("Balance on Current Chain: ", data);
  }, [data]);

  async function login() {
    if (!isAuthenticated) {
      await authenticate({ signingMessage: "Log in using Moralis" })
        .then(function (user) {
          console.log("logged in user:", user);
          console.log("account: ", Moralis.account);
        })
        .catch(function (error) {
          console.log(error);
        });
    }
  }

  const disconnect = async () => {
    await logout();
    console.log("logged out");
  };

  return (
    <div>
      {isAuthenticating && (
        <button className="btn btn-primary" disabled>
          Processing
        </button>
      )}
      {!isAuthenticating && !isAuthenticated && (
        <button className="btn btn-primary" onClick={login}>
          Connect
        </button>
      )}
      {!isAuthenticating && isAuthenticated && (
        <div>
          <button className="btn btn-primary mb-3" onClick={disconnect}>
            Disconnect
          </button>
          <p>user: {user?.id}</p>
          <p>account: {account}</p>
          <p>chainId: {chainId} </p>
          <p>native Balance: {data?.formatted}</p>
        </div>
      )}
    </div>
  );
}

1 Like

@qudusayo I like the simplicity. Thank you for sharing that!

One issue in your version: when you refresh the page, you lose the user object. How do you handle that?

Also, what is the best way to handle when the user logs out of MetaMask or does not have MetaMask installed? Are there Moralis hooks for that or do you go back to window.ethereum?

If you add isInitialized to the dependencies here, it should fix that of the user.

You can use WalletConnect rather

You could navigate to your main app page or display a notification on log out. If MetaMask is not installed (e.g. ethereum isn’t injected into the window object), you can detect this and add a message prompting to install it.

The easiest solution was just to check window.ethereum and of false, put up a button to install Metamask.

I have everything now working except one thing:

Refreshing the page causes strange behavior with {user, account} = useMoralis(). User object is still there but account goes to null. I can get the account insider user.attributes but the fact that it disappears from useMoralis makes me worried about what is happening on the inside.

Here is the code, two versions and the output. This happens on page refresh or navigating to another page, where I’m trying to retain the account, chainId and logged in status. I can refresh the account data by calling enableWeb3 each time, but that throws a different but non-fatal error.

Case 1:

const { Moralis, user, account, isAuthenticated, isInitialized } =
    useMoralis();
  const { chain } = useChain();

  useEffect(() => {
    if (!account) {
      Moralis.enableWeb3();
    }
    console.log(user);
    console.log(chain);
    console.log(account);
  }, [user, chain, account, Moralis]);

Here is the console:
Screen Shot 2022-05-27 at 10.51.14 PM

Now if I take out the Moralis.enableWeb3() there is no error but I lose the account data:

 const { Moralis, user, account, isAuthenticated, isInitialized } =
    useMoralis();
  const { chain } = useChain();

  useEffect(() => {
    // if (!account) {
    //   Moralis.enableWeb3();
    // }
    console.log(user);
    console.log(chain);
    console.log(account);
  }, [user, chain, account, Moralis]);

on the console:

Screen Shot 2022-05-27 at 10.51.33 PM

I also tried Moralis.isWeb3EnableLoading and Moralis.refetchUserData() but those have no affect at all.

Yes you are on the right track with enableWeb3 (account refers to address of the connected wallet so we need web3 enabled, whereas user is the Moralis user state).

You can use enableWeb3 and isInitialized from useMoralis() and run it like this to get the address on each refresh:


const { ..., isInitialized, user, account } = useMoralis();

useEffect(() => {
    enableWeb3();
}, [isInitialized]);

// testing
useEffect(() => {
    console.log('account', account);
}, [account]);
1 Like

@glad That is amazing! So much less code and it works perfectly! Thank you very much for your help. You can tell when someone really knows what they are doing, because in the end it looks simple. I was doing so many thing by trial and error.

1 Like

I’m using Moralis useNativeBalance() to get the balance of the currently logged in account. What I can’t figure out is how to force useNativeBalance to update when a transaction has finished, like sending some ETH to another account. I’m polling the receiving account to display the new receiving balance, but the sending account does not update. I tried refetchUserData but that has no effect.

What is the best way to monitor balances for a pending transaction while updating the display to show the user the from and to balances as the transaction processes?

When a transaction has finished you can use await tx.wait() - this will wait for the transaction to be confirmed on-chain (I think by default it’s 3 times). And then after that, you can call getBalances from useNativeBalance to re-fetch the balance (if this is all within the same component).

This example uses useWeb3Transfer as a test but you can apply it to other transaction methods e.g. Moralis.executeFunction():

function UpdateNativeBalance() {
  const { getBalances, data } = useNativeBalance();

  const { authenticate, isAuthenticated, isInitialized } = useMoralis();
  const transfer = useWeb3Transfer({
    amount: Moralis.Units.ETH(0.001),
    receiver: '0x0000000000000000000000000000000000000000',
    type: 'native',
  });

  useEffect(() => {
    if (isInitialized) {
      authenticate();
    }
  }, [isInitialized]);

  useEffect(() => {
    if (isAuthenticated) {
      send();
    }

    async function send() {
      const tx = await transfer.fetch();
      // can set pending status/state here
      await tx.wait(); // wait for transaction to be confirmed
      getBalances(); // re-fetch current balance
    }
  }, [isAuthenticated]);

  useEffect(() => {
    console.log('Balance', data);
  }, [data]);
}

If you want to update your app display, you can just set and use states (useState) appropriately.

1 Like

Thanks. I’m taking closer look at react-moralis. Looks like some great stuff in there that simplify a lot I’m trying to do.