How to call a smart contract using wagmi's useWriteContract hook
frontend#wagmiBlockchainSmart Contracts

How to call a smart contract using wagmi's useWriteContract hook

Interacting with smart contracts through your React based UI

Ignacio PastorIgnacio Pastor
January 2, 2026
6 min read

How do you call a smart contract function that performs a change of state (write) on the blockchain from the frontend? And how could we be sure that the write transaction went through correctly?

wagmi

If you have ever done Web3 frontend, then you must have heard about or used the wagmi library. Wagmi allows devs to make all kind of blockchain interactions on the frontend in a manner that feels very native to React, with a set of 40+ React hooks. From getting info about the connected wallet to smart contract interaction passing through fetching blockchain state data.

As you know, I'm a big fan of the 80/20 rule. For me that means that even if most of the functionality that wagmi has to offer is interesting, you will probably use around 20 percent of it on a regular basis. That leaves us with...8 hooks?

I'm not going to cajole this series of articles to fit that number but the plan is to talk about what I use most frequently at work. I'm also going to focus on doubts and questions that frequently come up so you can speed run your smart contract interaction tasks.

Talking about frequently asked questions, another of the reasons for this series is that the wagmi library, and especially the hooks, had significant breaking changes from v1 to v2. Some of the most annoying ones are the name changes of some of the hooks. This has nothing wrong, but most of your online searches in case you find a bug, even now, will probably yield outdated results even today. You can see the full migration guide in here

First, a bit of setup

I will assume that you are using a React-based framework (I'm using Next.js) with Typescript.

It is probable that you won't just install wagmi for your frontend project, but use it together with a wallet connector library that will give you a UI for connecting to different wallets and different chains. My favourites are Rainbowkit and Connectkit. I will be using the latter one for this example.

  1. Install Connectkit and dependencies, including wagmi
npm install connectkit wagmi viem@2.x @tanstack/react-query
  1. Create a custom Provider. We need to do a bit of provider-ception to get everything working

    1. The wagmi provider wraps the Tanstack query provider

    2. The Tanstack query provider wraps the Connectkit provider

    3. Those 3 providers make up our custom provider that wraps up any children component in our app.

I'm copying directly from the Connectkit documents, adding some extra comments to track everything. Don't forget to add use client if you are using Next.js

"use client"
import { WagmiProvider, createConfig, http } from "wagmi";
import { mainnet, sepolia } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ConnectKitProvider, getDefaultConfig } from "connectkit";

//wagmi config
const config = createConfig(
  getDefaultConfig({
    // Your dApps chains
    chains: [mainnet],
    transports: {
      // RPC URL for each chain
      [mainnet.id]: http(
        `https://eth-mainnet.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_ID}`,
      ),
    [sepolia.id]: http(
        `https://eth-sepolia.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_ID}`,
      ),
    },

    // Required API Keys
    walletConnectProjectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,

    // Required App Info
    appName: "Your App Name",

    // Optional App Info
    appDescription: "Your App Description",
    appUrl: "https://family.co", // your app's url
    appIcon: "https://family.co/logo.png", // your app's icon, no bigger than 1024x1024px (max. 1MB)
  }),
);

//Tanstack query provider instance
const queryClient = new QueryClient();

export const Web3Provider = ({ children }) => {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
      //Connectkit provider
        <ConnectKitProvider>{children}</ConnectKitProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
};

Finally, add your custom provider to your root layout.tsx file in your Next project.

"use client"
import "./globals.css";
import React from "react";
import { Web3Provider } from "./Web3Provider";

export default function RootLayout({children}: Readonly<{children:React.ReactNode;}>) {
      return (
          <html>
              <body>
                  <Web3Provider>
                      {children}
                </Web3Provider>
              </body>
        </html>
      );
};

Extra setup

Most of these libraries that help with connecting wallets use WalletConnect SDK, so you will need to go and create a projectID (you can reuse it across projects honestly) on this link

Also, WalletConnect SDK has some dependencies that are not used on Next.js by default (ever seen those "pino-pretty not found" errors?) you can either manually install pino-pretty in your project npm install pino-pretty or use this snippet in your next-config.js

module.exports = {
  webpack: (config) => {
    config.resolve.fallback = { fs: false, net: false, tls: false };
    return config;
  },
};

Call a smart contract

To call a smart contract using wagmi you will use useWriteContract. We first instantiate the hook and destructure the return.

const {data:hash, writeContract} = useWriteContract()

I normally pick up both data and of course writeContract

  • data will return undefined until you call writeContract. It returns the tx hash and it can be useful to feed it as input to other hooks like useWaitForTransactionReceipt which can in turn be used to design the next steps of the UX process.

  • writeContract is a function that you can use in other parts of the component, either directly in an onClick arrow function or a handleTx() type of function. You need to pass the contract interaction params to this function as an object.

    const handleMint = (address: string) => {
        console.log('Minting...');
        writeContract({
            address: process.env.CONTRACT_ADDRESS as Address,
            abi: erc20Abi,
            functionName: "mint",
            args: [address],
        })
    };

You can also manually change the config for this hook, instead of letting it pick up from the global config. If you do this, you probably will also want to use a hook like useSwitchChain to drive the user to connect to your target chain.

const config = createConfig({
    chains: [arbitrum]
    transports: {
        [arbitrum.id]: http()
    },
})

const {data:hash, writeContract} = useWriteContract({config})

How to handle next steps

If you only want to check if the tx was sent successfully or even if its confirmed, then the useContractWrite return values like onSuccess, onError or onSettled will be enough.

But some times you need to use the tx confirmation, the function return values or even an event emitted by the contract.

This is when useWaitForTransactionReceipt and useWatchContractEvent come in handy. I talk about the latter one on the [[How to listen for and parse smart contract events using wagmi | next article]] but lets see now how you would wait for the tx receipt event.

Wait for the tx receipt with wagmi

After calling a smart contract, you normally wait for some confirmation that your transaction went through and then you make some action.

This is a key element of blockchain UX, your users want to be sure that they did everything right and the action that they intended to make is completed.

This is when useWaitForTransactionReceipt comes into play. Remember the data return value in useWriteContract ? That was the tx hash of our contract call, and that's what we use as input on this hook.

const { data, writeContract } = useWriteContract();
const { data:receipt, isLoading: isConfirming, isSuccess: isConfirmed } = 
      useWaitForTransactionReceipt({hash: data as `0x${string}`});

The receipt object is a complex one, with lots of data about your transaction. Info about gas usage, transaction type and a logs object which you can decode to get data about events emitted by the transaction.

The logs field includes a data and a topics field. They are hex-encoded, but you can transform them to a human readable format using viem's lower level decodeEventLogs

const decodedLogs:any = decodeEventLog({
            abi: ERC20Abi,
            data: logs[0].data,
            topics: logs[0].topics,
          });

References

About the Author

Ignacio Pastor

Ignacio Pastor

Fullstack Web3 Developer focused on NFTs and blockchain gaming. I blend my passion for Solidity with a deep understanding of smart contract development. It's not just about writing code; it's about creating solutions that are innovative and reliable. My expertise extends across a spectrum of token standards, including ERC721, ERC1155, and the innovative ERC6551, unlocking new possibilities for token-gated communities, blockchain gaming, and NFT marketplaces