How to read from a smart contract using wagmi v3
#wagmiBlockchainFrontend Development

How to read from a smart contract using wagmi v3

Interacting with smart contracts through your React based UI

Ignacio PastorIgnacio Pastor
February 10, 2026
7 min read

Note: This article has been updated from the original version to reflect the breaking changes and new patterns introduced in wagmi v3. The core concepts remain the same, but the API syntax has been modernized to align with TanStack Query conventions and improve type safety.

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 article is that the wagmi library continues to evolve with breaking changes. After the significant changes from v1 to v2, wagmi v3 introduces a more streamlined API that better aligns with TanStack Query patterns. The main changes in v3 include:

  • Hook renames: useAccount -> useConnection (better reflects the connection model)

  • Mutation function standardization: hooks now return a mutation object with .mutate() and .mutateAsync() methods instead of direct functions

  • TypeScript requirement bumped to 5.7.3+

  • Connector dependencies are now optional peer dependencies (install only the connectors you use)

You can see the full v3 migration guide here

First, a bit of setup

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

Note: If you plan to use wallet connectors (WalletConnect, Coinbase, MetaMask, etc.), you now need to install their peer dependencies manually in v3.

  1. Install wagmi v3 and dependencies
npm install wagmi@3 viem@2 @tanstack/react-query
  1. Create a custom Provider. We need to set up two providers:

    1. The WagmiProvider provides blockchain context to your app

    2. The QueryClientProvider from TanStack Query manages data fetching and caching

Don't forget to add use client if you are using Next.js with the App Router.

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

// Create wagmi config
const config = createConfig({
  chains: [mainnet, sepolia],
  transports: {
          // RPC URL for each chain
    [mainnet.id]: http(
      process.env.NEXT_PUBLIC_ETH_MAINNET_RPC,
    ),
    [sepolia.id]: http(
      process.env.NEXT_PUBLIC_ETH_SEPOLIA_RPC,
    ),
    ),
  },
});

// Create TanStack Query client
const queryClient = new QueryClient();

export const Web3Provider = ({ children }: { children: React.ReactNode }) => {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        {children}
      </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>
      );
};

Wallet UI note for v3

wagmi v3 refactored connectors into optional peer dependencies. This reduces bundle size and lets you control wallet SDK versions, but it also means many wallet UI libraries (like Rainbowkit and Connectkit) still targeting wagmi v2 need time to update. The result is that v3 can look "unsupported" until you manually install connectors and pass them into createConfig.

If you only need injected wallets (MetaMask, Coinbase Wallet extension, etc.), a small custom button built with wagmi hooks is enough to unblock you while the ecosystem catches up.

Read from a single smart contract

Lets start reading data from a smart contract. You can access data from any function marked as view on a smart contract, and you don't even need that your user's wallet is connected to your application.

Oftentimes you will use these functions to check token ownership or balance, this is how it looks like.

export default function RandomComponent() {
  const { address, isConnected } = useConnection();
  const {
    data: balance,
    isLoading: isLoadingBalance,
    isError: errorBalance,
    isSuccess: successBalance,
  } = useReadContract({
    // APE coin
    address: "0x4d224452801ACEd8B2F0aebE155379bb5D594381",
    abi: erc20Abi,
    functionName: "balanceOf",
    args: [address],
    chainId: 1,
  })
  //...Rest of the component ...
}

Take into account:

  • If you are using balance or whatever data you have directly in your component, be sure to handle the undefined case well, as on the first render it will almost 100% return undefined.

  • wagmi uses Tanstack Query, so the retries are baked in. You don't need to add balance to a useEffect dependency array just to fetch the data.

  • If your output is a number, it will probably be a BigInt, handle with formatEther to get something readable.

I added chainId because, in my case, my app is configured to be able to read from both Sepolia and ETH Mainnet. Normally, wagmi hooks will fetch the config that you set up in the Provider, but if you need to read data from a different chain in just one specific component, you can define this config and only apply it to that contract call in the hook.

Example on how to read from the USDT contract on Arbitrum, just for this component. We first define the custom config for the component.

import { http, createConfig } from "wagmi"
import { arbitrum } from "wagmi/chains"

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

Then we just use this config on the hook

export default function RandomComponent() {
  const { address, isConnected } = useConnection();
  const { data: balance } = useReadContract({
    config,
    address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
    abi: erc20Abi,
    functionName: "balanceOf",
    args: [address],
    chainId: 42161,
  })
  //...Rest of the component ...
}

What happens if I need to wait for another piece of data as input for the read function?

Remember, if the hook has a dependency on the args array, just handle the undefined case.

Auto-refresh after writes

If a read depends on a write you just performed, you can refetch once the write confirms. This keeps the read UI fresh without polling.

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

const { refetch: refetchBalance } = useReadContract({
  address: tokenAddress,
  abi: erc20Abi,
  functionName: "balanceOf",
  args: [address],
});

useEffect(() => {
  if (!isConfirmed || !receipt) return;
  refetchBalance();
}, [isConfirmed, receipt, refetchBalance]);

Read from multiple smart contracts

You can also use wagmi's hooks to perform batch reads of different contracts, multiple functions inside the same contract or even multiple reads of the same smart contract with different input parameters.

To achieve this, we will use the useReadContracts hook, it works almost the same, but it takes an array of contract configs under the contracts key

const { data: balanceTest } = useReadContracts({
  contracts: [{
    address: "0x4d224452801ACEd8B2F0aebE155379bb5D594381",
    abi: erc20Abi,
    functionName: "balanceOf",
    args: [testAddress],
    chainId: 1,
  },
  {
    address: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
    abi: erc20Abi,
    functionName: "balanceOf",
    args: [testAddress],
    chainId: 1,
  }],
})

In this case, the response will be an array of objects, "[{result: 0n, status: success}]"

Of course, if you hold the list of contracts in a separate variable, you can use map to iterate over the whole list. This often looks cleaner and the code for the component ends up less cluttered. In this case you need to do a bit more of explicit type casting.

const { data: balanceTest } = useReadContracts({
  contracts: contractConfigs.map(contract => ({
    address: contract.address as `0x${string}`,
    abi: contract.abi as Abi,
    functionName: contract.functionName,
    args: contract.args,
    chainId: contract.chainId,
  })),
})

Example repo

I set up a repo with examples for this series of articles on my Github. Feel free to explore it and run it locally!

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