DEV Community

Cover image for Send React Web3 Transactions via MetaMask with useDapp
Jacob E. Dawson
Jacob E. Dawson

Posted on

Send React Web3 Transactions via MetaMask with useDapp

Today we're going to send real transactions via our React Dapp, both reading from & writing to an Ethereum smart-contract written in Solidity. If you followed the last tutorial Build a Web3 Dapp in React & Login with MetaMask, you'll already know how to connect an Ethereum account to a dapp - now we're getting to the good stuff: interacting with and changing the state of the Ethereum network!

By the end of today's tutorial you will know the following:

  • How to write a basic smart contract in Solidity
  • How to deploy that contract to a test network
  • How to import that contract into a frontend
  • How to read from & write to that contract

Tech Stack

In addition to the tech we used last time:

  • react ^17.0.2
  • typescript ^4.2.1
  • @usedapp/core ^0.4.1
  • @chakra-ui/react ^1.6.5

We'll also be making use of Solidity >0.5 and the Remix Solidity online editor.

NOTE: Before we start, make sure you have MetaMask installed and you are connected to the Ropsten test network.

Part 1: Creating & Deploying a Smart Contract via Remix

Step 1.) Create a basic contract in Remix

We're going to create a simple smart contract using the online editor Remix: https://remix.ethereum.org/. Make sure you're connected to the Ropsten network (via MetaMask), then navigate to the File Explorer tab, click on contracts and create a new file called SimpleContract.sol:

image

We're not going to spend too much time on explaining Solidity - it's essentially an object-oriented language with JavaScript-like syntax, where class is contract. There are many more subtle differences, but they are beyond the scope of this tutorial! (I'll add some links for learning more about Solidity at the end of this tutorial).

Inside SimpleContract.sol, add the following code:



pragma solidity >0.5; // pragma is always required

contract SimpleContract {
  uint256 public count;

  function incrementCount() public {}

  function setCount(uint256 _count) public {}
}


Enter fullscreen mode Exit fullscreen mode

In the above code we are initializing a variable called count as a uint256 integer type. When we declare this variable it is automatically set to 0.

You may notice that both our variable & function definitions include the keyword "public"; this is to give external accounts the ability to interact with them (other keywords include private, internal & external).

Another thing to note is that when we declare a public variable as we have with count, Solidity will automatically create a "getter" function with the same name for that variable - this contract now includes a function called count that we can use to read the count variable.

Lastly, you will see a notification on the Solidity compiler tab in Remix - Remix is automatically compiling the contracts that we write in the online editor, saving us a step (that we would normally have to initiate in our local development environment):

image

Next, let's add some logic to the two functions we created in SimpleContract.sol:



pragma solidity > 0.5;

contract SimpleContract {
    uint256 public count;

    function incrementCount() public {
        count++;
    }

    function setCount(uint256 _count) public {
        require(_count > 0);
        count = _count;
    }
}


Enter fullscreen mode Exit fullscreen mode

This is really all we need to get started - the incrementCounter function will increment the count variable when we call it, and the setCounter function lets us set the count variable.

Notice the line require(_count > 0); - require is a Solidity function that is essentially a conditional, whereby the condition _count > 0 must be true for the program flow to continue - if the condition is not true, the program execution will halt with an error.

You might see the compiler complaining here about a missing license definition - to fix that, simply add the following license identifier at the top of SimpleContract.sol:



// SPDX-License-Identifier: MIT


Enter fullscreen mode Exit fullscreen mode

Step 2.) Deploying our Smart Contract

Now we have a contract that we will be able to read from & write to - but in order to do that we will want to deploy the contract to the Ropsten test network (we use Ropsten for test purposes and also because we can use test ETH that does not cost us real-world money!).

Click the "Deploy & Run Transactions" tab in Remix, and change the "Environment" dropdown to "Injected Web3":

image

This will prompt your MetaMask extension to open, and show the "Connect With MetaMask" screen. Here you can select the account that you would like to connect with:

image

Click "Next", and then "Connect" on the next screen, where MetaMask asks if you would like to allow this site to "View the addresses of your permitted accounts". Once you do that, you will see that your Ethereum account is now connected to the Remix dapp:

image

You're going to need some test ETH to deploy the contract, so run over to the Ropsten testnet faucet, enter the account address that you've connected with, and request some test ETH. This should take a few seconds, but may take a little longer depending on how busy the faucet is.

So now we have a smart-contract written & compiled, and our Ethereum account connected to Remix. Let's now deploy our contract to Ropsten. While on the "Deploy & Run Transactions" tab, click the orange "Deploy" button - this is going to prompt your MetaMask to open, and this time you will need to spend some test ETH:

image

Scroll down and click "Confirm" to pay the gas required to deploy our SimpleContract. In the Remix console you'll see that the contract creation is pending:

image

This may take up to a minute, but soon you'll see a confirmation that the contract has been deployed!

image

Now, down the bottom of the Deploy & Run Transactions tab we can see some details about our deployed contract:

image

Here you can use the provided inputs to play with the contract, and test if it's working (in a professional workflow we would test our contracts during the creation process, but for this tutorial we won't need to go through that process).

Step 3.) Interacting with our Smart Contract via Remix

Click "Count" - you'll see that it triggers our automatically created "getter" function, and will show us the current state of the count variable. In the Remix console you'll also see that a CALL was made to our deployed contract, triggering the function and returning the variable value!

NOTE: When you deploy a contract, it will have an Ethereum address that cannot be changed*. We will need to store this address as we will use it in our frontend dapp. Contracts deployed to the Ethereum network are immutable - once they are deployed they cannot be changed. If you need to change something within a contract, you'll need to deploy a new version, which will also use a brand new contract address.

*There are "Upgrades Plugins" provided by OpenZeppelin, but I don't currently know enough about them to understand how they work, so for all intents & purposes we will regard our contracts as completely immutable.

You can copy the deployed contract's address from the Remix interface:

image

You can also view additional information about the address on the network it has been deployed on using Etherscan: https://ropsten.etherscan.io/address/[YOUR_CONTRACT_ADDRESS]

Before you continue testing the contract, it's also useful to know: reading from a contract is free, writing to a contract costs ETH. While on a test network this isn't an issue, you just have to make sure you have test ETH from the faucet - but writing to contracts on the mainnet is going to cost you real money.

If you click "incrementCount" you'll be prompted by MetaMask to confirm the fee - you're changing the state of the contract (and therefore the state of the entire Ethereum network), and this will cost you a gas fee. The transaction will then be pending, and eventually processed:

image

If you now click "count" again, you'll see that the count variable has indeed been incremented :)

Let's try the setCount function - add the number 99 in the field then click "setCount" - once again, MetaMask will open since we are writing to the smart contract state and therefore need to pay for the computation. After the transaction has been processed by the network, reading the count variable will now return 99 - everything is working as expected.

Remember that we set a require statement in our setCount function? Trying sending the number 0 as input to setCount and see what happens..

We've now completed Part 1 of this tutorial - writing & deploying a smart contract to to Ethereum network, and interacting with the contract. In Part 2 we're going to create a simple dapp with a custom UI that allows us to interact with our contract.

Part 2: Create a React Dapp & Connecting it to our Smart Contract

Step 1.) Setting up our project

To get into the good stuff, we'll start by creating a new branch from the UI we built in the previous tutorial. If you haven't already, clone that repo:



git clone https://github.com/jacobedawson/connect-metamask-react-dapp


Enter fullscreen mode Exit fullscreen mode

Then cd into connect-metamask-react-dapp

Run the following command to create a new branch:



git checkout -b send-transactions


Enter fullscreen mode Exit fullscreen mode

Then run:



yarn install


Enter fullscreen mode Exit fullscreen mode

So we are now in our React dapp repo called connect-metamask-react-dapp, on a branch called send-transactions. Run the following command to start up the project:



yarn start


Enter fullscreen mode Exit fullscreen mode

Make sure that you're on Ropsten Test Network, and that you have ETH in your account. Connect to the dapp and you should be seeing something like this (with your account & balance):

image

Our goals now are the following:

  • Connect the SimpleContract contract to our React UI
  • Read the count variable and display it in our interface
  • Add some inputs to our interface
  • Send transactions to update the count variable

Step 2: Import our Contract & Read a Value

We're using the useDapp framework in our app to provide a clean abstraction for interacting with the Ethereum network, and they provide the useContractCall and useContractCalls hooks to make custom calls to a smart contract.

In order to interact with a smart contract from our Dapp, we need two things: the contract address and an ABI (application binary interface).

Let's create a new folder in our src directory called contracts, and then create an index.ts file in that folder (remember, we are using TypeScript, not pure JavaScript).

Go to Remix and copy the address of the smart contract that you have deployed to the Ropsten network, and export it from the index.ts file:



export const simpleContractAddress = "0x5711aCCC813db3E27091acD1cae9Cc483721717C";


Enter fullscreen mode Exit fullscreen mode

Now we want to get the ABI of our contract from Remix. First, create a new folder in src called abi, and inside that create a new .json file called SimpleContract.json. In Remix, go to the "Compile" tab, and down below "Compilation Details" you will see a copy icon next to "ABI". Click it to copy the ABI, then paste it into abi/SimpleContract.json. It should look like this:



[
    {
        "inputs": [],
        "name": "incrementCount",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "_count",
                "type": "uint256"
            }
        ],
        "name": "setCount",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "count",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    }
]


Enter fullscreen mode Exit fullscreen mode

We can now import our contract address and ABI into any other file in our React project. Now we're going to create the component to display our "count" variable. In our components directory, create a file called Count.tsx - this is also where we'll add our inputs. Let's add a basic layout:



import { Flex, Text, Button } from "@chakra-ui/react";

export default function Count() {
  return (
    <Flex direction="column" align="center" mt="4">
      <Text color="white" fontSize="8xl"></Text>
      <Button colorScheme="teal" size="lg">
        Increment
      </Button>
    </Flex>
  );
}


Enter fullscreen mode Exit fullscreen mode

And import Count into App:



import Count from "./components/Count";

// ...other code
<Layout>
  <ConnectButton handleOpenModal={onOpen} />
  <AccountModal isOpen={isOpen} onClose={onClose} />
  <Count />
</Layout


Enter fullscreen mode Exit fullscreen mode

We should now see the following:

image

Step 3: Create a Hook to Read Values from our Contract

Our "Increment" button doesn't do anything yet, and we also aren't pulling in any data from our smart contract. In order to do this we're going to create a hook. Create a folder called hooks inside the src directory, and then create the file index.ts inside the hooks folder. Inside hooks/index.ts we'll import our contract address & ABI, as well as a useContractCall hook from useDapp and a utils object from Ethers:



// hooks/index.ts
import { ethers } from "ethers";
import { useContractCall } from "@usedapp/core";
import simpleContractAbi from "../abi/SimpleContract.json";
import { simpleContractAddress } from "../contracts"

const simpleContractInterface = new ethers.utils.Interface(simpleContractAbi);

export function useCount() {
  const [count]: any = useContractCall({
    abi: simpleContractInterface,
    address: simpleContractAddress,
    method: "count",
    args: [],
  }) ?? [];
  return count;
}


Enter fullscreen mode Exit fullscreen mode

If you're not familiar with the double question mark syntax (known formally by its very catchy name "Nullish coalescing operator"), if the left-hand side operand is null or undefined, it will return the right-hand operand, so if our useContractCall is undefined, the count variable we destructure will be undefined (since the right-hand operand is an empty array).

We're using the Interface constructor from Ethers to create a new Interface instance, which we'll need to pass to the useDapp hook useContractCall. Let's import the hook into our Count.tsx file:



// Count.tsx
import { Flex, Text, Button } from "@chakra-ui/react";
import { useCount } from "../hooks";

export default function Count() {
  const count = useCount();
  return (
    <Flex direction="column" align="center" mt="4">
      <Text color="white" fontSize="8xl">
        {count ? count.toNumber() : 0}
      </Text>
      <Button colorScheme="teal" size="lg">
        Increment
      </Button>
    </Flex>
  );
}


Enter fullscreen mode Exit fullscreen mode

If the count variable is undefined, we'll show 0, otherwise we'll display the count variable. Notice that we're converting the value returned by useCount into a regular JavaScript number using the .toNumber method - our smart contract function is actually returning a BigNumber object, so we need to transform it so that React can display it.

image

Depending on current state of your contract, the number that you'll now see on the screen will differ. To check that we're reading up-to-date information directly from our contract, go to Remix and use the "setCount" function to set the count to something distinctive. Send the transaction and in a few seconds you will see the React UI display the updated count:

image

We're making good progress now! We're importing our contract, reading a value from it and displaying it in our UI. The next step is to be able to send transactions from our UI and update the state of the smart contract.

Step 4: Send Transactions & Update our Smart Contract

First we'll add a new hook into our hooks/index.ts file. We're also going to import useContractFunction from useDapp, and the Contract constructor from ethers. The imports in hooks/index.ts should look like this:



import { ethers } from "ethers";
import { Contract } from "@ethersproject/contracts";
import { useContractCall, useContractFunction } from "@usedapp/core";
import simpleContractAbi from "../abi/SimpleContract.json";
import { simpleContractAddress } from "../contracts";


Enter fullscreen mode Exit fullscreen mode

We're going to use the Contract constructor to create an instance of our contract, and to do that we need to pass in both our contract address and our newly created interface:



// hooks/index.ts
const contract = new Contract(simpleContractAddress, simpleContractInterface);


Enter fullscreen mode Exit fullscreen mode

Now let's create our hook (we'll call it "useIncrement"), which makes use of useDapp's useContractFunction hook:



export function useIncrement() {
  const { state, send } = useContractFunction(contract, "incrementCount", {});
  return { state, send };
}


Enter fullscreen mode Exit fullscreen mode

The useContractFunction hook takes in our contract instance, the name of the method that we'd like to call, and an options object. It returns an object with two variables - state and send, which we are destructuring from the function call. Lastly, we export our useIncrement hook so that we can use it anywhere in our dapp.

That's all we need for now, so let's move over to Count.tsx, import the new useIncrement hook, and create a "handleIncrement" function, which we'll add to our Button component's onClick handler:



// Count.tsx
import { Flex, Text, Button } from "@chakra-ui/react";
import { useCount, useIncrement } from "../hooks";

export default function Count() {
  const count = useCount();
  const { state, send: incrementCount } = useIncrement();

  function handleIncrement() {
    incrementCount();
  }

  return (
    <Flex direction="column" align="center" mt="4">
      <Text color="white" fontSize="8xl">
        {count ? count.toNumber() : 0}
      </Text>
      <Button 
        colorScheme="teal" 
        size="lg" 
        onClick={handleIncrement}>
        Increment
      </Button>
    </Flex>
  );
}


Enter fullscreen mode Exit fullscreen mode

Now we're ready to test out our "Increment" button. Make sure that the dapp is running, and the click "Increment". Your MetaMask extension should open up with a prompt, where you can set a gas price for the transaction and then confirm you want to send the transaction:

image

Once you confirm the transaction it will be signed using our private key and sent to the Ethereum network, where it will be processed (mined) quickly by a node, added to a block, broadcast to the network and added to the updated network state - after all of that, we should see our interface increment the count variable by 1. It may seem like a lot of work just to see a number increase by 1, but actually we've now covered most of the basics of web3 development - connecting to an account, reading values and sending transactions!

We're almost done - let's take this opportunity to go one step further and send a value input by the user to our smart contract...

Step 5: Sending User Input to our Smart Contract

For the last part of today's tutorial, we're going to accept user input in our UI and send it to our setCount smart contract function.

We'll start by adding another hook to hooks/index.ts called "useSetCount":



// hooks/index.ts
export function useSetCount() {
  const { state, send } = useContractFunction(contract, "setCount", {});
  return { state, send };
}


Enter fullscreen mode Exit fullscreen mode

This is essentially the same as our existing useIncrement hook - the only difference is the method name. That gives us an opportunity to create a more generic hook that takes a method name and returns { state, send } - let's create a single hook "useContractMethod" that will replace both useIncrement and useSetCount:



// hooks/index.ts
export function useContractMethod(methodName: string) {
  const { state, send } = useContractFunction(contract, methodName, {});
  return { state, send };
}


Enter fullscreen mode Exit fullscreen mode

Now we can delete useIncrement and useSetCount and import useContractMethod into our Count.tsx component:



// Count.tsx
import { useCount, useContractMethod } from "../hooks";

// pass method names to useContractMethod
const { state, send: incrementCount } = useContractMethod("incrementCount");
const { state: setCountState, send: setCount } = useContractMethod("setCount");


Enter fullscreen mode Exit fullscreen mode

Notice how we are also changing the variable names for some of the destructured values from useContractMethod. Now that we've imported the methods, we'll import useState to hold the input state, add an input component and another button in order to collect user input, and send it to the setCount method. Here's the final, complete code for Count.tsx:



// Count.tsx
import { useState } from "react";
import {
  Box,
  Flex,
  Text,
  Button,
  NumberInput,
  NumberInputField,
} from "@chakra-ui/react";
import { useCount, useContractMethod } from "../hooks";

export default function Count() {
  const count = useCount();
  const { state, send: incrementCount } = useContractMethod("incrementCount");
  const { state: setCountState, send: setCount } =
    useContractMethod("setCount");
  const [input, setInput] = useState("");

  function handleIncrement() {
    incrementCount();
  }

  function handleSetCount() {
    const _count = parseInt(input);
    if (_count) {
      setCount(_count);
    }
  }

  function handleInput(valueAsString: string, valueAsNumber: number) {
    setInput(valueAsString);
  }

  return (
    <Flex direction="column" align="center" mt="4">
      <Text color="white" fontSize="8xl">
        {count ? count.toNumber() : 0}
      </Text>
      <Button colorScheme="teal" size="lg" onClick={handleIncrement}>
        Increment
      </Button>
      <Box mt={4}>
        <NumberInput
          mb={2}
          min={1}
          value={input}
          onChange={handleInput}
          color="white"
          clampValueOnBlur={false}
        >
          <NumberInputField />
        </NumberInput>
        <Button isFullWidth colorScheme="purple" onClick={handleSetCount}>
          Set Count
        </Button>
      </Box>
    </Flex>
  );
}



Enter fullscreen mode Exit fullscreen mode

At this point we should have our entire UI for this tutorial ready to go:

image

Our "NumberInput" will only accept numbers, and we've coded logic into our component to only fire the setCount method if our count value is 1 or more. Let's test it out by setting the count to 500 and clicking "Set Count":

image

All good! That's it - well done if you've made it this far - we've managed to achieve all of our goals for this tutorial:

  • How to write a basic smart contract in Solidity
  • How to deploy that contract to a test network
  • How to import that contract into a frontend
  • How to read from & write to that contract

Hopefully this tutorial has shown you how easy it is to get started with dapp development. If you spot any bugs or have any questions, leave a comment below and I'll get back to you asap!

In the next tutorial, we'll learn how to import a pre-existing 3rd party contract and build an alternate interface for it..

Thanks for playing ;)

Helpful Resources & Frameworks

https://ethereum.org/en/whitepaper/
https://usedapp.readthedocs.io/en/latest/
https://ethereum.org/en/developers/tutorials/
https://ethernaut.openzeppelin.com/
https://eth.build/
https://hardhat.org/tutorial/
https://www.quicknode.com/guides/defi
https://chakra-ui.com/
https://solidity-by-example.org/

Full Repo (send-transactions branch)

Follow me on Twitter: https://twitter.com/jacobedawson

Top comments (22)

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!