
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.
Sometimes, you need to listen for an event emitted by a smart contract in your frontend. Often after making a contract call so the UI can advance to the next step of the process. So, how do we do this?
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 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 functionsTypeScript 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.
- Install wagmi v3 and dependencies
npm install wagmi@3 viem@2 @tanstack/react-query
Create a custom Provider. We need to set up two providers:
The
WagmiProviderprovides blockchain context to your appThe
QueryClientProviderfrom 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 made connectors optional peer dependencies. This gives you more control over wallet SDKs but requires you to install and wire them manually. Wallet UI kits (like Rainbowkit and Connectkit) are still adapting to this change, which is why v3 adoption looks slower in the ecosystem.
For injected-only flows, a small custom connect button using wagmi hooks is often the simplest, most reliable option.
Listen and parse smart contract events
The hook that you will use for creating a smart contract event listener on your frontend is useWatchContractEvent.
useWatchContractEvent({
address: process.env.SMART_CONTRACT_ADDRESS,
abi: ERC20Abi.abi,
eventName: "Minted",
onLogs(logs) {
console.log("Contract event log:", logs)
},
});
This hook does not have a return value (well, void) so all the information received through it has to be handled through the onLogs callback.
Logging the contract event logs like we do in this example will render the whole event object, with things like eventName and args that might be useful to extract information that you may need in your frontend, like fetching the tokenID of the new NFT that has been minted (transferred from the address zero) to your user or the amount of ERC20 that went from one EOA to another.
Manually decoding event logs
If, for any reason, you need to decode the raw event data received from your smart contract, you can use the lower level viem's decodeEventLog and use it on your useWatchContractEvent hook onLogs callback. You will need to pass it your contract's abi, the data and topics fields that you received in the contract event object.
useWatchContractEvent({
address: process.env.SMART_CONTRACT_ADDRESS,
abi: ERC721Abi.abi,
eventName: "Minted",
onLogs(logs) {
const decodedLogs: any = decodeEventLog({
abi: ERC721Abi.abi,
data: logs[0].data,
topics: logs[0].topics,
});
const tokenIdString = decodedLogs?.args?.tokenId.toString();
setTokenId(tokenIdString);
},
});
As you see, I'm using the callback to update a React state variable that will be used elsewhere in my component. You can use this hook together or instead of useWaitForTransactionReceipt to wait for the "response" from the smart contract when making a contract call. Both of these hooks include in their responses logs corresponding to the events emitted. The difference is that useWaitForTransactionReceipt is more targeted, as it only deals with one specific transaction hash, while useWatchContractEvent will deal with any events emitted by the contract, no matter who initiated that transaction.
This is why you want to be mindful when using this hook, as you could accidentally get data from a different user than the one currently using the UI.
Auto-refresh after events
If your UI depends on read hooks (balances, ownership, supply), you can refetch them when a relevant event arrives. This keeps the UI in sync without manual refreshes.
useWatchContractEvent({
address: tokenAddress,
abi: erc20Abi,
eventName: "Transfer",
onLogs() {
refetchBalance();
},
});
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
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
More Articles


