Working with contracts + wagmi
24 июня 2024 г.
Below are examples of working with smart contracts using wagmi.
Example data
For this example, we will use the USDT contract on the Binance testnet:
https://testnet.bscscan.com/token/0x337610d27c682E347C9cD60BD4b3b107C9d34dDd
Token contract address:
0x337610d27c682E347C9cD60BD4b3b107C9d34dDd
You can download the contract ABI from the Contract tab via the link above. It contains all available methods and their parameters. The ABI is required for interacting with the contract.
Reading a contract
Reading a contract is similar to a GET request. Some contract methods require arguments, others do not.
To read a contract, you can use:
- useReadContract hook
- or readContract method
useReadContract hook
This hook is used like any other React hook, inside a component or a custom hook.
Currently, there is a TypeScript issue: returned data has type unknown. A workaround is explicitly casting the type.
1import { useAccount, useReadContract } from "wagmi";2import { tokenAbi } from "@/constants/abi/tokenAbi";345export const useTokenDecimals = (6 tokenAddress: `0x${string}`,7) => {8 const { chainId } = useAccount();910 const { data: decimals } = useReadContract({11 abi: tokenAbi,12 address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",13 functionName: "decimals",14 chainId: chainId,15 });1617 const tokenDecimals = decimals as number;1819 return {20 tokenDecimals,21 };22};23
functionName is the contract method (here: decimals). It returns token decimals, which are used for formatting amounts.
Here is an example of a formatting function that takes decimals as an argument.
readContract method
Currently imported from @wagmi/core:
1import {readContract} from "@wagmi/core";
Этот метод можно вызывать в любом месте, в котором вам необходимо.
Example: when sending a transaction, you may need to check allowance of a token contract. It requires arguments: user wallet address and contract address.
Here config is WalletConnect config. Example (Next.js app router):
1const allowance = await readContract(config, {2 abi: tokenAbi,3 address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",4 functionName: "allowance",5 chainId: chainId,6 args: [userAddress, contractAddress],7});8
1export const config = defaultWagmiConfig({2 chains,3 projectId,4 metadata,5 ssr: true,6 storage: createStorage({7 storage: cookieStorage8 })9})10
Writing to a contract
writeContract method
To send data to a contract, use writeContract. It is similar to a POST/PUT request.
1import { writeContract } from "@wagmi/core";
You pass the same parameters as in readContract: ABI, contract address, function name, and arguments (from ABI).
Example: if token allowance is less than the amount being sent, you must first call approve:
1if (amount && +allowanceAmount < amount) {2 const approveTx = await writeContract(config, {3 abi: tokenAbi,4 address: tokenAddress,5 functionName: "approve",6 args: [7 yourContract,8 formatNumberToWEI(amount, tokenDecimals),9 ],10 });11}
Here config is WalletConnect config.
Here, we pass the recipient contract address and the amount formatted in wei as arguments. The transaction hash will be assigned to the approveTx constant.
writeContract. documentation.
useWriteContract hook
You can also use useWriteContract, which exposes:
- writeContract
- transaction hash
- status
- and more
useWriteContract. documentation.
Transaction status
To check the transaction status and determine whether it was successful, you can use the waitForTransactionReceipt method. Currently, this method is also imported from @wagmi/core.
1import { waitForTransactionReceipt } from "@wagmi/core";
The transaction hash returned from the writeContract method is passed to it. After that, the returned result can be processed as required.
1const approveTx = await writeContract(config, {2 abi: tokenAbi,3 address: tokenAddress,4 functionName: "approve",5 args: [6 yourContract,7 formatNumberToWEI(amount, tokenDecimals),8 ],9});1011const approveReceipt = await waitForTransactionReceipt(config, {12 hash: approveTx,13});1415if (approveReceipt.status !== `success`) {16 setTransactionLoading(false);17 throw new Error("Approve error");18}
Here config is WalletConnect config.
waitForTransactionReceipt. documentation.
Full example: allowance + approve
1import { useAccount } from "wagmi";2import {3 writeContract,4 waitForTransactionReceipt,5 readContract,6} from "@wagmi/core";7import { useCallback, useState } from "react";89<..>1011const { chainId, address } = useAccount();12const [transactionLoading, setTransactionLoading] =13 useState<boolean>(false);1415<..>1617const approve = useCallback(18 async (19 amount: number,20 tokenAddress: `0x${string}`,21 tokenDecimals: number22 ) => {23 setTransactionLoading(true);24 const allowance = await readContract(config, {25 abi: tokenAbi,26 address: tokenAddress,27 functionName: "allowance",28 chainId: chainId,29 args: [address, yourContract],30 });313233 const allowanceAmount = formatNumberFromWEI(34 allowance as bigint,35 tokenDecimals36 );373839 if (amount && +allowanceAmount < amount) {40 const approveTx = await writeContract(config, {41 abi: tokenAbi,42 address: tokenAddress,43 functionName: "approve",44 args: [45 yourContract,46 formatNumberToWEI(amount, tokenDecimals),47 ],48 });495051 const approveReceipt = await waitForTransactionReceipt(config, {52 hash: approveTx,53 });545556 if (approveReceipt.status !== `success`) {57 setTransactionLoading(false);58 throw new Error("Approve error");59 }606162 return approveReceipt;63 } else {64 return {65 status: "allow",66 };67 }68 },69 [address, chainId, yourContract]70 );71
Sending a transaction with raw data
There may be cases when, to send a transaction, you only have the transaction data — the hashed contract method with encoded arguments — and to — the transaction recipient or your contract address.
In this case, you can use the sendTransaction method. At the moment, it is imported from @wagmi/core.
1import { sendTransaction } from '@wagmi/core'23const result = await sendTransaction(config, {4 to: to as `0x${string}`,5 data: data as `0x${string}`,6});
Here config is WalletConnect config.
result returns the transaction hash.
sendTransaction documentation.