How to interact with smart contracts using Wagmi in React

In this tutorial, we are going to learn how to interact with smart contracts using Wagmi in React. How to read data from smart contracts and send transactions that call write functions.

In this tutorial, we are going to learn how to interact with smart contracts using Wagmi in React. How to read data from smart contracts and send transactions that call write functions.

We can use useContractWrite for write functions, useContractRead for read functions (or public/external variables), and useContract to get a Contract instance and do a lot more things.

Here is an example:

import { usePrepareContractWrite, useContractWrite, useContractRead, useContract, erc20ABI } from 'wagmi';
import { utils } from 'ethers';

const contractAddress = '0x326C977E6efc84E512bB9C30f76E30c160eD06FB';
const testAddress = '0x5b8f1310A956ee1521A7bB56160451C786289aa9';

export function InteractWithContract() {
  // Prepare the transaction
  const { config, error: contractWriteError } = usePrepareContractWrite({
    address: contractAddress,
    abi: erc20ABI,
    functionName: 'transfer',
    args: [testAddress, utils.parseEther('100')],
  });

  // Get the write function
  const { data: writeData, isLoading: writeLoading, write } = useContractWrite(config);

  // Read values from the smart contract
  const { data: readData, isLoading: readLoading } = useContractRead({
    address: contractAddress,
    abi: erc20ABI,
    functionName: 'balanceOf',
    args: [testAddress],
  });

  console.log(contractWriteError);

  return (
    <div>
      {readData && (
        <p>
          The address {testAddress} has {utils.formatEther(readData)} LINK
        </p>
      )}
      {readLoading && <p>Loading the balance data...</p>}
      {writeLoading && <p>Please confirm the transaction on your wallet</p>}
      {writeData && <p>The transaction was sent! Here is the hash: {writeData.hash}</p>}
      {!writeLoading && (
        <button disabled={!write} onClick={() => write()}>
          Write function
        </button>
      )}
      {contractWriteError && (
        <p>
          Calling that contract function will fail for this reason:
          {contractWriteError.reason ?? contractWriteError.message}
        </p>
      )}
    </div>
  );
}

How to read data from a smart contract

To read data from a smart contract (in other words, calling functions that return data), we use the useContractRead hook.

To make it work, you need to pass an object in the parameters with these 3 properties:

  • address: the address of the smart contract you want to interact with
  • abi: the ABI of the smart contract containing the functions you want to call
  • functionName: the name of the function to call
  • args: an array containing the arguments to pass to the function you're calling. You can leave it empty if you don't need to pass any parameters

Check out the documentation to see all the other properties you can pass.

That hook will return an object and inside that object, the properties that we use the most are:

  • data: the value returned by the smart contract function
  • isLoading: a boolean that is true while fetching the data and false otherwise
  • error: the error thrown if fetching the data failed or null if there was no error

Again, check out the documentation to see all the other properties returned by the hook.

To sum up, if you want to read data from a smart contract, use the useContractRead hook and pass the address and ABI of the contract you want to interact with, the name of the function to call and the arguments to pass.

That hook will return a data property containing the value returned by that function.

Here is an example calling the balanceOf function of an ERC-20 token (the LINK token on the Goerli network):

import { useContractRead, erc20ABI } from 'wagmi';
import { utils } from 'ethers';

const contractAddress = '0x326C977E6efc84E512bB9C30f76E30c160eD06FB';
const testAddress = '0x5b8f1310A956ee1521A7bB56160451C786289aa9';

export function ReadContract() {

  // Call the getBalance function of the smart contract
  const { data: readData, isLoading: readLoading } = useContractRead({
    address: contractAddress,
    abi: erc20ABI,
    functionName: 'balanceOf',
    args: [testAddress],
  });
  
  if (readLoading) return <p>Loading...</p>

  return (
    <div>
      {readData && (
        <p>
          The address {testAddress} has {utils.formatEther(readData)} LINK
        </p>
      )}
    </div>
  );
}

How to call multiple read functions at once

Sometimes, you might want to call multiple read functions from the same smart contract at once to get all the data you need at the same time.

The first obvious solution would be to just use multiple useContractRead hooks but it's not clean and it generates lots of repeated code. Instead, we can group all the calls using the useContractReads hook.

The useContractReads hook is exactly like useContractRead but the difference is that in the parameters, there is a contract property in which you can pass an array of objects.

Each object in that array will be a read function call. The properties of that object are the same properties you would pass to useContractRead.

For each function call, you need to pass the contract address, the ABI, the name of the function to call and the arguments to pass to the function (just like useContractRead).

Here is an example in which I get multiple pieces of information about an NFT (Bored apes):

import { erc721ABI } from 'wagmi'
import { useContractReads } from 'wagmi'

const nftContractAddress = '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D';
const testAddress = '0x5b8f1310A956ee1521A7bB56160451C786289aa9';

export function GetAllDataFromContract() {
  
  const { data, error, isLoading } = useContractReads({
    contracts: [
      {
        // Get the total number of NFTs available
        address: nftContractAddress,
        abi: erc721ABI,
        functionName: 'totalSupply',
        args: [],
      },
      {
        // Get the name of the NFT collection
        address: contractAddress,
        abi: erc721ABI,
        functionName: 'name',
        args: [],
      },
      {
        // Get the number of NFTs that an address owns
        address: contractAddress,
        abi: erc721ABI,
        functionName: 'balanceOf',
        args: [testAddress],
      },
    ]
  })
  
  if (isLoading) return <p>Loading data...</p>
  
  if (error) return <p>Could not fetch data</p>
  
  return (
    <div>
      {data && (
        <>
          <p>Total supply: data[0]</p>
          <p>NFT name: data[1]</p>
          <p>The address {testAddress} owns {data[2]} NFTs</p>
        </>
      )}
    </div>
  )
}

The useContractReads hook will return the following properties (among others):

  • data: an array containing the results of each function call in the same order that we called them in the parameters of the hook.
  • error: the error thrown while calling the functions if the function calls failed. If there was no error, it's null.
  • isLoading: will be true while fetching the data and false otherwise

Check out the documentation to learn more about the arguments you can pass to that hook and the values that it returns.

In the example above, I fetch data from the same contract but you can also use that hook to fetch data from multiple smart contracts.

For example, you can use it to fetch the token balances of an address. You would simply call the balanceOf function of multiple token smart contracts.

How to call a write function of a smart contract and send a transaction

Before diving in, it is important to know the difference between read and write functions.

A read function will just return data from a smart contract and not change any data which costs no gas.

A write function on the other hand, will change data in a smart contract which costs gas so these function calls are actually transactions. That means these transactions are not free and will cost some gas. You can also send Ethereum to the smart contract with these functions.

So the reason there's a distinction between these 2 types of functions is because reading data is free and changing data costs Ethereum (gas fees).

To call a write function of a smart contract using Wagmi, we use the usePrepareContractWrite and useContractWrite hooks.

First we call use the usePrepareContractWrite hook and in the parameters, we pass an object.

That object requires the same properties as the useContractRead hook which I described above.

So you need to pass the contract address, the ABI, the function to call and the arguments you want to pass.

It will return an object containing a config which is what you need to pass to the useContractWrite hook. The error property that it returns is also very important because it will tell you if calling that function is expected to fail or not.

If you want to learn more about errors, check out this tutorial on how to handle errors when sending transactions.

So, once you have the config of your function call, you can use the useContractWrite hook and pass the config in the parameters. It will return an object with the following properties:

  • data: A TransactionResponse object containing information about the transaction you just sent.
    It will contain a hash property which is the hash of the transaction you just sent and a wait property which is a function that allows you to wait for the transaction to complete.
    It will be null before the transaction is sent.
  • write: The function to call to actually send the transaction and call the write function. It will require the user to confirm the transaction on the wallet that is connected to your website.
    If no wallet is connected, that property will be null.
  • isLoading: true after calling the write function and while waiting for the user to confirm the transaction on their wallet. It is false otherwise.
  • error: the error thrown while trying to send the transaction to the blockchain. For instance, if the user rejects the transaction on their wallet, that property will contain a value. If there was no error, it is null.

If you want to learn more about the wait function in the data property, check out our tutorial on how to wait for a transaction to complete using Wagmi.

Here is an example calling the transfer function of an ERC-20 token contract (which sends the tokens from an address to another):

import { usePrepareContractWrite, useContractWrite, erc20ABI } from 'wagmi';
import { utils } from 'ethers';

const contractAddress = '0x326C977E6efc84E512bB9C30f76E30c160eD06FB';
const testAddress = '0x5b8f1310A956ee1521A7bB56160451C786289aa9';

export function CallWriteFunction() {
  // Configure the write function call
  const { config, error } = usePrepareContractWrite({
    address: contractAddress,
    abi: erc20ABI,
    functionName: 'transfer',
    args: [testAddress, utils.parseEther('100')],
  });

  // Get the write function
  const { data, isLoading, error: writeError, write } = useContractWrite(config);
  
  if (isLoading) return <p>Please confirm the transaction on your wallet</p>
  
  if (writeError) return <p>Could not send the transaction</p>
  
  if (error) return <p>The transaction is expected to fail with this error: {error.reason ?? error.message}</p>
  
  if (data) return <p>The transaction was sent, Here is its hash: {writeData.hash}</p>

  return (
    <div>
      {!writeLoading && (
        <button disabled={!write} onClick={() => write()}>
          Write function
        </button>
      )}
    </div>
  );
}

How to send Ethereum along with calling a smart contract write function

When calling a write function of a smart contract, if that function is marked as payable, you can send Ethereum to the smart contract when you call that function.

The most common use case is when minting an NFT. Most NFTs have a price when they are minted so you want the users to send Ethereum to the smart contract to pay for that NFT when they mint it.

To do that, we need to pass an extra property called overrides in the object that we pass to usePrepareContractWrite.

The overrides property is an object that can have a value property which contains the amount of Ethereum to send when calling the function. The amount you pass here needs to be in Wei.

As a reminder, Wei is the smallest sub-unit of Ethereum and that's the unit that smart contracts and the blockchain use to represent Ethereum amounts. It's an integer that has 18 decimals of precision.

Here is an example calling the mint function of an NFT smart contract:

import { usePrepareContractWrite, useContractWrite } from 'wagmi';
import { utils } from 'ethers';

export function CallWriteFunctionAndSendETH() {

  const { config, error } = usePrepareContractWrite({
    address: '0xBE86EB679C791D0b4dC98A4fEbbbE86C74Eed872',
    abi: [
      'function mint(uint256 mintAmount) payable'
    ],
    functionName: 'mint',
    args: [1], // mint 1 NFT
    // send ETH according to the price of the NFT:
    overrides: {
      value: utils.parseEther('0.1'),
    },
  });

  const { data, isLoading, error: writeError, write } = useContractWrite(config);
  
  // ... REST OF THE COMPONENT
}

As you can see, since the value is expected to be in Wei, we need to use the utils of the Ethers JS library to convert the Ethereum from Ether to Wei.

For that, I use the parseEther function which takes in parameter the amount of Ether to convert to Wei as a string.

How to get contract logs and all the other information about a smart contract

Lastly, we can use the useContract hook to get a Contract instance and do a lot more things with the smart contract like getting the logs that the contract generated.

That hook takes in parameter an object that needs to contain 2 properties:

  • address: the address of the smart contract to interact with
  • abi: the ABI to use to interact with the smart contract

Using that instance to get logs or do other things is a separate topic that the one that is being covered here and this article cannot cover everything. So if you want to see everything you can do with that instance, check out the Ethers JS documentation.

However, I have a few tutorials that can help you:

If you want to learn more about contract events and logs, check out our tutorial on how to get the logs of a smart contract and listen for events using Wagmi

The Contract instance also allows you to estimate the amount of gas it will cost you to call a write function of a smart contract. If you want to learn how to do that, check out our tutorial on how to estimate gas fees using Wagmi.

And that's it 🎉

Thank you for reading this article