Best way to listen for events in react-moralis

Iā€™m working with moralis to send transactions, and Iā€™m working with useWeb3Contract which gives us:

data: The response of running the contract function
error: An error in the transaction
isFetching: The transaction is in flight?
isLoading: The metamask hasnā€™t been approved yet?

However, it doesnā€™t seem to give us an option for us to listen for a transaction to complete. It looks like data gives a transactionResponse type object that has a wait function which is nice, but no way to listen.

Looking at how the ethereum boilerplate handles events they donā€™t use the hooks at all, they use the ā€œrawā€ Moralis.executeFunction functionality, but Iā€™m not sure if this is best?

How would I wait for events when using the hooks like useWeb3Contract?

1 Like

I am having the same issue.

2 Likes

Hey @PatrickAlphaC @DappThatApp, you can use onSuccess callback to listen for events from the tx object (same value populated in data), which will trigger once the transaction is completed

useWeb3Contract({
  // other options
  onSuccess: (tx) => {},
});

Another way to listen for events with Moralis is to use Event Syncing which will capture all emitted events real-time and populate them to the Database table, which then can be listened with useMoralisSubscription whenever new rows is added

To clarify, isFetching is to indicate whether the transaction is still running or not, and isLoading is basically checking isFetching && data === null (thereā€™s more details).

Cheers~

1 Like

This looks promising. I want to try this in my code. However, in my solidity code, i am trying to create an erc721 token, which requires metamask signing for the transaction. I am having problems getting the metamask wallet to sign the transaction as part of the call to useWeb3Contract.

More specifically, how do I get metamask to open when I call runContractFunction?

I think that you have to use enableWeb3 before that, and metamsk should pop up automatically

Currently, Im using the function below to mint nftā€™s and get the confirmed transaction response. It works, and I get the token Id that I need, but I was wanting to use the Moralis apiā€™s so i can get used to them.

This code launches metamask and waits for the confirmed response. How would i do this using useWeb3Contract?

const mintNft = async () => {

        const ethers = Moralis.web3Library
    
        const web3 = await Moralis.enableWeb3({ provider: "web3Auth", chainId: '0x3', clientId:
        "redacted" });
        const signer = web3.getSigner()
        const contract = new ethers.Contract(TOKEN_CONTRACT_ADDRESS, abi, signer)
        
        const transaction = await contract.createItem(uri)
        const tx = await transaction.wait()
        const event = tx.events[0]
        const value = event.args[2]
        const tokenId = value.toNumber()
        
        return tokenId
        
  }

this may be a case that is not handled now by Moralis.executeFunction, you could try with Moralis.executeFunction to see if it works

I see. So, useWeb3Contract doesnt support function calls that require a signed wallet transaction?
I just want to make sure I understand correctly. Thanks for your help

Amazing. Iā€™ll try this out this week. Thanks!

@DappThatApp @PatrickAlphaC on a related note, are you using event listeners to check if user is on the wrong chain etc?
I successfully handled most things like change in account and a case of manual disconnecting metamask from the dapp by using {account} = useMoralis(); which causes re-render anytime account is changed.
But I canā€™t seem to figure out the onChainChanged event listener. If you can copy paste that bit of your code it will help.

1 Like

hey @zubin. the way to handle chain changes you can request metamask to handle chain changes whenever the user clicks on some button to change which uses a metamask request option to change chain by triggering a popup forcing the user to change. on the other hand if a user just decides to change chain then you can use an onChainChange event handler to trigger the popup automatically with useEffect also. the code below shows an example of how to do this for the example of ethereum, ploygon and binance smart chain. this is even more powerful implemnted with web3react. or mroalis if you desire

import { useState, useEffect } from "react";

const networks = {
  polygon: {
    chainId: `0x${Number(137).toString(16)}`,
    chainName: "Polygon Mainnet",
    nativeCurrency: {
      name: "MATIC",
      symbol: "MATIC",
      decimals: 18
    },
    rpcUrls: ["https://polygon-rpc.com/"],
    blockExplorerUrls: ["https://polygonscan.com/"]
  },
  bsc: {
    chainId: `0x${Number(56).toString(16)}`,
    chainName: "Binance Smart Chain Mainnet",
    nativeCurrency: {
      name: "Binance Chain Native Token",
      symbol: "BNB",
      decimals: 18
    },
    rpcUrls: ["https://bsc-dataseed1.binance.org"],
    blockExplorerUrls: ["https://bscscan.com"]
  }
};

const changeNetwork = async ({ networkName, setError }) => {
  try {
    if (!window.ethereum) throw new Error("No crypto wallet found");
    await window.ethereum.request({
      method: "wallet_addEthereumChain",
      params: [
        {
          ...networks[networkName]
        }
      ]
    });
  } catch (err) {
    setError(err.message);
  }
};

export default function App() {
  const [error, setError] = useState();

  const handleNetworkSwitch = async (networkName) => {
    setError();
    await changeNetwork({ networkName, setError });
  };

  const networkChanged = (chainId) => {
    console.log({ chainId });
  };

  useEffect(() => {
    window.ethereum.on("chainChanged", networkChanged);

    return () => {
      window.ethereum.removeListener("chainChanged", networkChanged);
    };
  }, []);

  return (
    <div className="credit-card w-full lg:w-1/2 sm:w-auto shadow-lg mx-auto rounded-xl bg-white">
      <main className="mt-4 p-4">
        <h1 className="text-xl font-semibold text-gray-700 text-center">
          Force MetaMask network
        </h1>
        <div className="mt-4">
          <button
            onClick={() => handleNetworkSwitch("polygon")}
            className="mt-2 mb-2 btn btn-primary submit-button focus:ring focus:outline-none w-full"
          >
            Switch to Polygon
          </button>
          <button
            onClick={() => handleNetworkSwitch("bsc")}
            className="mt-2 mb-2 bg-warning border-warning btn submit-button focus:ring focus:outline-none w-full"
          >
            Switch to BSC
          </button>
         
        </div>
      </main>
    </div>
  );
}


the above example is basic and only supports thos two chains. to make it more robust you should integrate this web3react or moralis (which you are jsing) and use this hook to get the current chain. something like

const { chainId } = useWebReact()

or for your case

const { cahinId } = useMoralis

if you then, in the useffect with the chainChain event listener add the dependancy of the chainId then evrry time you switch networks the popup will automatically show, that is if you call the handleNetworkChange function inside the useEffect.

2 Likes

@mcgrane5 thanks so much for the detailed reply. I will try implementing this way!

@mcgrane5 Hereā€™s what I did:

  1. In my App.js I added an if statement to an existing useEffect in order to change the network immediately on Metamask login.
useEffect(() => {
    const connectorId = window.localStorage.getItem("connectorId");
    if (isAuthenticated && !isWeb3Enabled && !isWeb3EnableLoading){
        enableWeb3({ provider: connectorId });}
    if (chainId != '0xa86a') {
      switchNetwork("0xa86a")
    }

So far so good.
2. In order to catch the edge case of user absent mindedly switching network again after login (perhaps while working on another tab with Ethereum), I added a new useEffect with an event listener as you suggested.

useEffect(()=>{

    if(chainId != '0xa86a'){

      const unsubscribed = Moralis.onChainChanged((chain)=>{
        if (window.confirm(`You're on ${chain}! Click OK to switch to Avalanche!`)) {
          switchNetwork("0xa86a")
        } else {
          alert("You're on the wrong network & will result in loss of funds due to failed transaction! Switch to Avalance C-chain manually in your Metamask Wallet!");
        }
        unsubscribed();
    }); }
  }, [chain])

Trouble is, this extra useEffect keeps getting triggered even if I am already on Avax. Iā€™ve tried all kinds of permutations with the If statement. Any idea what Iā€™m doing wrong? image

Edit:
I solved it by removing the above useEffect in #2 and including chain in the array of the original useEffect.

useEffect(() => {
    const connectorId = window.localStorage.getItem("connectorId");
    if (isAuthenticated && !isWeb3Enabled && !isWeb3EnableLoading){
        enableWeb3({ provider: connectorId });}
    if (chainId != '0xa86a') {
      if (window.confirm(`You're on the wrong network! Click OK to switch to Avalanche C-chain!`)) {
              switchNetwork("0xa86a")
            } else {
              alert("You're on the wrong network & will result in loss of funds due to failed transaction! Switch to Avalance C-chain manually in your Metamask Wallet!");
           }
    }  
  }, [isAuthenticated, isWeb3Enabled, chain]);

This way if user is not on Avax it prompts and then changes the chain to Avax.

2 Likes

useWeb3Contract({
// other options
onSuccess: (tx) => {},
});

This fires the moment you hit the confirm button on Metamask. Itā€™s pretty much useless if you are trying to use it as a callback for when a tx has been confirmed successfully.

Hi, Patrick please tell me you tried this or found another way?

I cannot for the life of me figure out how to listen or track a successful tx confirmation using react-moralis.
Iā€™m using the useWeb3ExecuteFunction hook to mint my nfts but no way to figure out whether it got successfully minted.

Yep! I found out you have to add the onSuccess when you call the function. For example, if this is where you define your function:

    const {
        runContractFunction: enterRaffle,
        data: enterTxResponse,
        isLoading,
        isFetching,
    } = useWeb3Contract({
        abi: abi,
        contractAddress: raffleAddress,
        functionName: "enterRaffle",
        msgValue: entranceFee,
        params: {},
    })

Then youā€™d add your success function like so:

                    <button
                        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded ml-auto"
                        onClick={async () =>
                            await enterRaffle({
                                onSuccess: handleSuccess,
                            })
                        }
                        disabled={isLoading || isFetching}
                    >

And here is my func:

    const handleSuccess = async (tx) => {
        await tx.wait(1)
        updateUIValues()
        handleNewNotification(tx)
    }
2 Likes

Wow dude, it works perfectly. Thank you very much!

Thanks to the Moralis team for helping me find the answer!!!

1 Like

@orangemat enterRaffle is the contract function name right? Shouldnā€™t it be async()=> await fetch and not enterRaffle? Unless youā€™ve declared fetch: enterRaffle in the useWebExecuteFunction?

Fetch is equivalent to ā€œrunContractFunctionā€ here. Also, you need to use the useWeb3Contract hook instead of the useWeb3ExecuteFunction one.

2 Likes