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 withabi
: the ABI of the smart contract containing the functions you want to callfunctionName
: the name of the function to callargs
: 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 functionisLoading
: a boolean that istrue
while fetching the data andfalse
otherwiseerror
: the error thrown if fetching the data failed ornull
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'snull
.isLoading
: will betrue
while fetching the data andfalse
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
: ATransactionResponse
object containing information about the transaction you just sent.
It will contain ahash
property which is the hash of the transaction you just sent and await
property which is a function that allows you to wait for the transaction to complete.
It will benull
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 benull
.isLoading
:true
after calling thewrite
function and while waiting for the user to confirm the transaction on their wallet. It isfalse
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 isnull
.
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 withabi
: 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