@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.
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.
@mcgrane5 Hereās what I did:
- 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?
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.
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)
}
Wow dude, it works perfectly. Thank you very much!
Thanks to the Moralis team for helping me find the answer!!!
@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.
Both are basically the same. UseWeb3ExecuteFunction is working well for me. Itās just changing/deactivating the button after confirmation of txns that is the problem. And keeping it deactivated on a page refresh. Difference between `useWeb3ExecuteFunction`, `useApiContract`, `executeFunction`, `useWeb3Contract`, and `Moralis.executeFunction`
Ok, I got it so far to run funcitons depending onSuccess, onComplete or onError, but the values
of given data like error are undefined. I guess you did the trick with te tx.wait but I cant get it working to assing the useWeb3Contract with a tx and therefore wait.
Could you please tell me how you defined your tx in this example?
I mean with this code the handleError is called, but the if (error) statement results always in the else because error is undefined
const { runContractFunction: enterRaffle, data: enterTxResponse, error, isLoading, isFetching } = useWeb3Contract({
abi: abi,
contractAddress: contractAddress,
functionName: "addAddress",
params: {
_addressToBeAdded: account
},
}
);
const InteractContract = async () => {
await enterRaffle(
{
onSuccess: handleSuccess,
onComplete: handleComplete,
//onError: () => { handleError(JSON.stringify(error)) },
onError: handleError
}
)
if (error) {
//error occured
alert(error?.message.substring(error?.message.search('message') + 10, error?.message.search('data') - 3))
} else {
alert("success")
}
}
Hello michimich^^. Just kidding, will answer my own question, because I just figured it out.
for listening on the tx and therefure execute a wait it could be done like this:
const tx = await enterRaffle(
{
onSuccess: (tx) => tx.wait(1).then(handleSuccess),
onComplete: handleComplete,
// //onError: () => { handleError(JSON.stringify(error)) },
onError: handleError,
}
)
so then the handleSuccess is only called if the transaction was successfull. This works fine for me, will ad the same on the other handles as well.
Also I figured out, that a require in my contract can lead to a instant response with the error message, will make that clear with some screenshots if I have them.
Hope this could help others
So onSuccess
is when the transaction goes through, which is nice, what about when the transaction gets at least 1 block confirmation? Does the API work for that too?
Oh Iām idiot, you do await tx.wait(1)
.
Exactly what I said above XD
FYI for anyone it might be helpful to (as things are often use case dependent) - you can also just chain the event
eg.
runContractFunction(params)
.then(data => {
//do something with data
}).catch(err =>{
//handle error
})
hi, please the āenterTxResponseā in your code references to what if i was to use usdt has my contract?
Hello there,
I think I just linked a variable on my side to work with the the given data then (man this code doesn“t look pretty good).
In general (Patrick mentioned this at the beginning of this thread):
data: The response of running the contract function
I can“t remember exactly, because I changed it to be more clear. After the mint() function ran I go on and handle the result with either an error message or a success messag. This is what I came up with (if this helps you):
const { runContractFunction: mint } = useWeb3Contract({
abi: abi,
contractAddress: process.env.REACT_APP_NFT_CONTRACT_ADDRESS,
functionName: "mint"
}
);
//calling the mint function by using the useWeb3Contract()
await mint({
params: options,
onSuccess: (tx) => (handleSuccess(tx)),
onError: (tx) => (handleError(tx)),
});
//handle success
const handleSuccess = async (tx) => {
await tx.wait(1);
console.log("success entered")
console.log("tx", tx);
return ['success', tx];
}
//deal with an occured error
const handleError = async (tx) => {
var createdErrorMessage;
console.log("tx from interaction", tx);
if (tx.error !== undefined) {
createdErrorMessage = tx.error.message;
console.log("filtered error message", createdErrorMessage);
}
else if (tx.message !== undefined) {
//tx not fired, could be user cancel transaction
createdErrorMessage = tx.message;
console.log("tx message", tx.createdErrorMessage);
}
else {
createdErrorMessage = "undefined error occured";
}
console.log('error', createdErrorMessage, "Error");
return createdErrorMessage;
}
Greetings