DEV Community

Cover image for Send React Web3 Transactions via MetaMask with useDapp

Send React Web3 Transactions via MetaMask with useDapp

Jacob E. Dawson on July 20, 2021

Today we're going to send real transactions via our React Dapp, both reading from & writing to an Ethereum smart-contract written in Solidity. ...
Collapse
 
intergalactictrash profile image
intergalactictrash

Great tutorial! If I clone your repo, npm install, npm start, and then click the "increment" button, I get an unsupported chain error (see pic). At first I thought it was something I missed/modified while following this tutorial, but I still get the error when using your repo as-is.

Also, it appears to be changing the state of count appropriately. Everything is working on the blockchain/smart contract side as expected (ropsten.etherscan.io/tx/0xe6cd6480...), but the UI crashes. Anyone else having this issue?

Collapse
 
davidjamescarter profile image
David James Carter

Yes me too :/

Great tutorial though! :)

Collapse
 
jacobedawson profile image
Jacob E. Dawson

Hi - sorry for the late reply, tbh I'm not really sure about that, if I get the chance I'll spin the project back up and see what the issue might be.

Collapse
 
georgeflatline profile image
GeorgeFlatline

hey, thanks so much for the tutorial, it is great. I am stuck on one section, how do I modify the code to be able to call a payable contract function with a value? Thanks again for such a clear explanation.

Collapse
 
jacobedawson profile image
Jacob E. Dawson

Hi George,

With useDapp, in order to call a payable function and pass along a value we are using the useContractFunction method, which will automatically forward arguments to the underlying ethers.js contract object - this means that we don't need to explicitly do anything other than just pass the value, like we do inside the call to setCount inside the Count component:

function handleSetCount() {
const _count = parseInt(input) {
if (_count) {
setCount(_count); // passing _count here, which is handled by useContractFunction and sent through to the contract by ethers under the hood
}
}
}

Collapse
 
jacobedawson profile image
Jacob E. Dawson

Btw here's the specific documentation for useContractFunction: usedapp.readthedocs.io/en/latest/c...

Thread Thread
 
georgeflatline profile image
GeorgeFlatline

Hi Jacob, thanks for the reply. I did have a read of the documentation but I am still a little stuck! I am able to call the contract with arguments when it is not expecting a value for the message but when it is expecting a value I am still not able to trigger this.

If I call it like this:

mintButton.tsx

import {useContractMethod } from "../hooks";

const { state: setMintTeaState, send: mintIt } = useContractMethod("mintTea");

function handleMint() {
    if (account) {
        mintIt(account, 1);
    }
}
Enter fullscreen mode Exit fullscreen mode

index.ts

export function useContractMethod(methodName: string) {

const { state, send } = useContractFunction(contract, methodName, {});

return { state, send };
Enter fullscreen mode Exit fullscreen mode

}

I am able to mint a token if the contract function is not expecting payment (the first 200 tokens are free). If I try beyond the first 200 I get an error in metamask, MetaMask - RPC Error: execution reverted: Value below price.

I have tried passing in a value as a third argument to mintIt() e.g.

function handleMint() {
    if (account) {
        mintIt(account, 1, ethers.utils.parseEther("50000000000000000"));
    }
}
Enter fullscreen mode Exit fullscreen mode

but when I run this metamask does not pop up a transaction as it does when I run it without the third argument.

The contract abi for the mintTea function looks like this and takes just two arguments, the address and the number to mint.

{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_count",
"type": "uint256"
}
],
"name": "mintTea",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},

and here is the mintTea and price functions from the Solidity contract:

function mintTea(address _to, uint _count) public payable saleIsOpen {
    if(msg.sender != owner()){
        require(!paused, "Paused");
    }
    require(totalSupply() + _count <= MAX_TEA, "Max limit");
    require(totalSupply() < MAX_TEA, "Sale end");
    require(_count <= 15, "Exceeds 15");
    require(msg.value >= price(_count), "Value below price");

    for(uint i = 0; i < _count; i++){
        _safeMint(_to, totalSupply());
    }
}

function price(uint _count) public view returns (uint256) {
    uint _id = totalSupply();
    // free 200
    if(_id <= 229 ){
        require(_count <= 1, "Max 1 Free Per TX");
        return 0;
    }

    return 50000000000000000 * _count; // 0.05 ETH
}
Enter fullscreen mode Exit fullscreen mode

Sorry for such a long question, I am still very new to dapp development in general so am probably missing something quite basic! Any suggestions would be greatly welcomed, I have been trying various things to get this to work for a few days now but feel like I may be missing something obvious.

Thread Thread
 
jacobedawson profile image
Jacob E. Dawson • Edited

Hi George - the value property is a special property that is added to the msg object that the contract function receives - you can pass in your arguments sequentially and add a configuration object as the last argument:

mintTea(arg1, arg2, {
value: PARSED_ETH_VALUE
});

Here's a concrete example - I updated the contract I used in this tutorial with a payable function called "takeTwoVariables":

    function takeTwoVariables(uint _valueA, uint256 _valueB) public payable {
        require(msg.value > 0);
        count = _valueA + _valueB;
    }
Enter fullscreen mode Exit fullscreen mode

Inside Count.tsx, I've imported utils from ethers (so we can parse our value):

// Count.tsx

import { utils } from "ethers";
Enter fullscreen mode Exit fullscreen mode

And I've added a new function called "handleTwoVariables":

  function handleTwoVariables() {
    const _count = parseInt(input);
    if (_count) {
      setTwoVariables(_count, 2, {
        value: utils.parseEther("0.05"),
      });
    }
  }
Enter fullscreen mode Exit fullscreen mode

I replaced "handleSetCount" with "handleTwoVariables" in the onClick handler, so when the user clicks "Set Count" we will send 0.05 ether to the payable function along with the 2 integers (_count & 2). If the value property is not set, or set to 0, the function will revert because in the contract there is now a require statement, so the value must be > 0.

So, the TLDR is that you add arguments sequentially (based on their defined order in the contract), and you add "special" properties on the msg object like "value" inside the config object.

I hope that makes sense! Feel free to ask any more questions if that isn't quite clear :)

Thread Thread
 
georgeflatline profile image
GeorgeFlatline

Thank you so much. That works perfectly and makes sense now. Thanks for taking the time to reply in such detail, it has really helped!

Collapse
 
snowyfield1906 profile image
SnowyField

I had to create account to thank you
sir, you are really the legend
thank you very much

Collapse
 
jacobedawson profile image
Jacob E. Dawson

Wow, thanks! Happy to hear it helped you :)

Collapse
 
michaeln profile image
Michael

Hi Jacob, wonderful tutorial. Thank you.

I was wondering if there is a way to get the count from the contract without being connected with MetaMask. Currently the count only shows up if connected. If not connected, it defaults to 0. Thanks again.

Collapse
 
emerns02 profile image
emerns02

Hello! Great tutorial - extremely thorough and helpful for a newbie just getting into React.

I'm adapting your code to fit my smart contract - and I'm having trouble using the useContractCall hook. It works perfectly well when I have a single input address to pass to it (along with the ABI) - the problem is I have a 2-tiered smart contract that has a parent contract and then children contracts are created from the parent. I have an array of children contracts, but when I try and iterate through the array using the useContractCall hook, React barks at me that I "can't use a hook in a callback function".

Any ideas how to pass an array of contracts through to the useContractCall hook?

Thanks for the help!

Collapse
 
noisetheory profile image
noisetheory

Hey Jacob, your tutorials have been awesome.

I am having trouble with what is probably a newbie error. I am trying to query a read function on my contract that returns a list of token id's that are held by the connected wallet address.

My GetTokenIds function below is a repurposed version of your "getCount" function in this tut.

When I hard code the wallet address, it works ok, eg:
const wallet_address = "0x6Ff6**hidden*****C05";
const [token_ids]: any = GetTokenIds(wallet_address) ?? [];

but when i try to pass "account" to GetTokenIds it errors, i think because initially the account is 'undefined' for a brief moment at first.

How do i only execute the GetTokenIds function above when account is a valid wallet address?

Thanks so much

Collapse
 
awoodward profile image
Andy Woodward

Great tutorial. And comments!

Any ideas for trapping errors? When a "require" condition fails in the contract, I get an "RPC Error: execution reverted" message on the Javascript console, but there doesn't seem to be anyway to capture it. I tried try {] catch {} and no luck. Any ideas?

Much appreciated!

Collapse
 
inbell1s profile image
Inbell1s

Hi, how would one go about creating a transaction to a payable function?
The ABI of said function is:
{
"inputs": [
{
"internalType": "uint256",
"name": "_quantity",
"type": "uint256"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
}

Collapse
 
johnlee profile image
john

hello! I had some errors after 'transaction action'!
And after refreshing the page by force, it will return ok! Why?

Collapse
 
intergalactictrash profile image
intergalactictrash • Edited

A lot of people have also been having this issue (github.com/EthWorks/useDApp/issues...). It appears to be an issue with with Metamask using EIP-1559 type transactions.

FIXED: See the workaround provided here (github.com/EthWorks/useDApp/issues...).

If you're following this tutorial:

1.) Simply copy/paste the code provided in the workaround link into a new file "workaround.ts".
2.) At the top of src/hooks/index.ts, import useContractFunction from your new workaround.ts file instead of from "@usedapp/core".
3.) Note where the variable/const "contract" is assigned in src/hooks/index.ts. Then replace that line with the below code.

declare const window: any;
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner()
const counterContractInterface = new ethers.utils.Interface(counterContractAbi);
export const contract = new Contract(counterContractAddress, counterContractInterface, signer);

Collapse
 
jacobedawson profile image
Jacob E. Dawson

Hey - thanks a lot for sharing that info, I only just saw these recent comments! I believe a lot of issues are being causes in Dapps at the moment due to interactions between MetaMask regarding EIP-1559, hopefully they'll be resolved soon.

Collapse
 
yougotwill profile image
Will G

Hi Jacob, just finished going through your beginners post and this one! Awesome tutorials and I'm very grateful to you for making them. Thank you!

Collapse
 
rewin81 profile image
ReWin • Edited

Hi, Fantastic guide! I used this guide to use the minting feature of my contract, also following the comments. However, I have a problem: my contract has a cost for the minting of 0.02 ETH and if I set this number I am able to mint only 1 NFT. If I increase this number (for example 0.06) I am able to coin 3 NFT but the problem is that if I want to coin only 1 I always pay 0.06! Can you help me correct this error?

function handleSetCount() {
const _count = parseInt(input);
if (_count) {
mint(_count, {
value: utils.parseEther("0.02"),
});
}
}

Collapse
 
fabiojesus profile image
Devpool

Hi Jacob. Loved the tutorial. Recently I've decided to try to develop a project on BSC, which required me to update the usedapp package. I've deployed my contract and followed the exact steps to somewhat replicate the same behavior as the one shown on your tutorial. It worked fine until I had a function that required the passage of arguments. It is done in the same way as the setCount function, but when I press the button, I get an error instead saying that the argument is undefined. On an attempt to figure out if I had done anything wrong during development, I've pulled the code from your repo, updated the usedapps and attempted to call the setCount, which resulted in the same error.

Do you have any clues into either solving this?

Best regards,
FΓ‘bio Jesus