Dive Deep into Wagmi & How to Create a Web3 App

Glitch
8 min readOct 20, 2023

--

Ever wondered how websites can communicate with blockchain nodes? In this article we will dive deep into how you can interact with any EVM compliant blockchain, starting from a low level approach to a more abstracted and effiecient way of implementation.

How do we get data from the blockchain?

You can communicate with any EVM blockchain by sending JSON-RPC requests. If you would like to get information from a blockchain or interact with a smart contract, we first need to send a request to a blockchain node. This can be done by obtaining its RPC url endpoint and then by sending a JSON-RPC request with a method and parameters.

The following is a small example of how a JSON-RPC request to a blockchain node could look like using JavaScript

const axios = require("axios");

const payload = {
jsonrpc: "2.0",
id: 0,
method: "eth_getBalance",
params: ["0x8303539291922EF29B518B5B93e8Ab07F22F2D1d","latest"]
};

(async () => {
const response = await axios.post(`https://eth-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_KEY}`, payload)
console.log(response.data)
})();

For simple requests there is not much to worry about but when we want to make more complex calls or interact with smart contracts we need to send data in the form of bytecode and we’ll also need to decode the responses we receive.

This is one of the many problems we’ll encounter when trying to interact with a blockchain node, and there are already some in-built libraries that offer quick solutions to address these problems. The most popular ethereum libraries for typescript are ethers.js, viem and web3.js. In this article we’ll focus on wagmi and its underlying library viem and the best way to implement it in a web application.

Viem

Viem delivers a great developer experience through modular and composable APIs, comprehensive documentation, and automatic type safety and inference. It was built with a strong focus on stability, performance and a small bundle size.

Viem offers smart abstractions over the JSON-RPC API and multiple utilities to simplify your work when interacting with EVM compatible blockchains.

Viem is optimized to be used for different purposes and environments such as scripts, bots, servers, websites and more.

Tanstack Query

As we saw in our first JSON-RPC request example, we used axios (a library that abstract http requests) to send a JSON-RPC request. Similarly we can use tools or libraries created for the web to optimize fetching, caching, synchronizing, error handling and more.

One of the best libraries to help us on the frontend side with this is Tanstack Query, a data-fetching and state management library for web applications that simplifies fetching, caching and updating data.

Wagmi

Wagmi is an all-in-one library that combines different libraries to simplify the interaction of any web application with the blockchain. We can split Wagmi in four main parts:

  • Connectors
    Wagmi abstracts the logic needed to interact with wallets and handles the sessions and user’s connections for you
  • Viem
    Wagmi integrates Viem using the best practices to interact with the blockchain and abstracts it in a way that makes it very simple to get the best out of it.
  • Tanstack Query
    Wagmi wraps Viem functions with Tanstack Query for requests optimization, error handling, caching and more.
  • Zustand
    Zustand is a very small package used as a global state manager, it will help us store different information like the user’s connection status, supported chains, and more. It will also interact with the client’s localStorage.

How to Create a Web3 App

Let’s now build a very simple web application that can help us communicate with the blockchain. We are going to create a website for minting ERC20 tokens.

1. Deploying an ERC20 token

Let’s create a very simple ERC20 token smart contract and deploy it to the blockchain.

Now let’s head over to Remix, a smart contract IDE, to deploy our contract to the blockchain. Under the contracts folder, we’ll create a new file called token.sol and paste our contract there.

Now on the left sidebar we’ll select the Solidity symbol and press the “Compile token.sol” button

Once that’s done we can select the Ethereum icon and under Environment we’ll choose the “Injected Provider — MetaMask” option. You’ll need a browser extension wallet with some native tokens to connect and deploy the contract. Now in your wallet select the blockchain that you want to deploy the contract to, I’m going to be using Goerli for this example. Once that’s set, press the “deploy” button and confirm the transaction in your wallet.

Once our smart contract is deployed we are going to be able to see our contract address in the Remix’s console.

2. Building our Web3 App

Let’s create our app now, we’re going to be using Next.js, Wagmi and Web3Modal v3 for this example

Let’s start a new project by running

npx create-next-app

Now we’ll install Wagmi and Web3Modal which will help us to showcase a modal with different wallets that users can connect to.

npm install @web3modal/wagmi wagmi viem

Let’s also install react-hot-toast library which will help us notify our users about the transactions status.

npm install react-hot-toast

On our App file we’ll start by importing the required dependencies from Web3Modal and Wagmi

import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react'

import { WagmiConfig } from 'wagmi'
import { goerli } from 'wagmi/chains'

We now need to get a Project ID from WalletConnect’s Cloud website

const projectId = 'YOUR_PROJECT_ID'

Once that’s done we can create our wagmiConfig instance, since we deployed our contract in Goerli, that’s the chain we’re going to use here

const metadata = {
name: 'Web3Modal',
description: 'Web3Modal Example',
url: 'https://web3modal.com',
icons: ['https://avatars.githubusercontent.com/u/37784886']
}
const chains = [goerli]
const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata })

Now we can create a Web3Modal instance to initiate our modal

createWeb3Modal({ wagmiConfig, projectId, chains })

Lastly we’ll wrap our app around the WagmiConfig component, this is how our _app.tsx should look like

import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react'

import { WagmiConfig } from 'wagmi'
import { goerli } from 'wagmi/chains'

// 1. Get projectId
const projectId = 'YOUR_PROJECT_ID'

// 2. Create wagmiConfig
const metadata = {
name: 'Web3Modal',
description: 'Web3Modal Example',
url: 'https://web3modal.com',
icons: ['https://avatars.githubusercontent.com/u/37784886']
}
const chains = [goerli]
const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata })

// 3. Create modal
createWeb3Modal({ wagmiConfig, projectId, chains })

export default function App({ Component, pageProps }: AppProps) {

return (
<WagmiConfig config={wagmiConfig}>
<Component {...pageProps} />
</WagmiConfig>
)
}

NOTE: if you are working on a production application it is recommended using your own RPC URL providers. When using defaultWagmiConfig function, internally it will add the WalletConnectProvider and the Wagmi’s Public provider. You can add extra providers using a Web3Modal custom configuration.
When adding different providers Wagmi will use Viem’s fallback function internally to create a stronger client, that will fall back to the next provider in the list if a request fails.

Now we will create a component, you can give it any name, I’ll call it TokenButton, we are going to add all the minting logic here.

We’ll start by importing useAccount, useBalance and our token’s ABI. useBalance is a Wagmi hook that is going to help us get the user’s balance of the token we have deployed.

import React from 'react'
import { useAccount, useBalance} from 'wagmi'
import { tokenAbi } from '../abis/tokenAbi'

const tokenAddress = '0x05049E822f3b978ceD140C9A8f2B4d158572AF42'
const TokenButton = () => {

const { address } = useAccount()

const { data, refetch } = useBalance({
address: address,
token: tokenAddress,
})

return (
<div>
UserBalance: { data?.formatted } { data?.symbol }
</div>
)
}

export default TokenButton

Now we are going to create a mint function, let’s import useContractWrite to interact with the smart contract. If you pay close attention, you will notice the outputs from some Wagmi hooks are very similar to the ones from React Query, Viem is wrapped with this library!

import React from 'react'
import { useAccount, useBalance, useContractWrite } from 'wagmi'
import { parseEther } from 'viem'
import { tokenAbi } from '../abis/tokenAbi'

const tokenAddress = '0x05049E822f3b978ceD140C9A8f2B4d158572AF42'
const TokenButton = () => {

const { address } = useAccount()

const { data, refetch } = useBalance({
address: address,
token: tokenAddress,
})

const { write, data: writeData } = useContractWrite({
address: tokenAddress,
abi: tokenAbi,
functionName: 'mint'
})

function mint(){
if(address)
write({ args: [address, parseEther('1')] })
}

return (
<div>
UserBalance: { data?.formatted } { data?.symbol }
<br />
<button onClick={mint}>Mint</button>
</div>
)
}

export default TokenButton

We are going to use the write function to trigger the mint transaction, we can pass the arguments in the useContractWrite hook or if the arguments are dynamic (like the user’s address) we can pass them down to the write function instead.

Now we are going to add the useWaitForTransaction hook to track the status of the transaction and also the react-hot-toast library we’ve previously installed to notify our users if the transaction was successful or there was an error.

import React from 'react'
import { useAccount, useBalance, useContractWrite } from 'wagmi'
import { parseEther } from 'viem'
import { tokenAbi } from '@/abis/tokenAbi'
import toast, { Toaster } from 'react-hot-toast';
import { useWaitForTransaction } from 'wagmi'

const tokenAddress = '0x05049E822f3b978ceD140C9A8f2B4d158572AF42'
const TokenButton = () => {

const { address } = useAccount()

const { data, refetch } = useBalance({
address: address,
token: tokenAddress,
})

const { write, data: writeData } = useContractWrite({
address: tokenAddress,
abi: tokenAbi,
functionName: 'mint',
onError:()=>toast.error("An Error Occurred")
})

const { isLoading } = useWaitForTransaction({
hash: writeData?.hash,
onSuccess:()=>{
toast.success("Token Minted!")
refetch()
},
onError:()=>toast.error("An Error Occurred")
})

function mint(){
if(address)
write({ args: [address, parseEther('1')] })
}

const label = isLoading ? "Minting" : "Mint a Token"
return (
<div>
UserBalance: { data?.formatted } { data?.symbol }
<br />
<button onClick={mint}>{label}</button>
<Toaster/>
</div>
)
}

export default TokenButton

Once that’s done we can import our TokenButton component into the index file

import styles from '@/styles/Home.module.css'
import TokenButton from '@/components/blockchain/TokenButton'
import { Hydrate } from '@/components/utils/Hydrate'

export default function Home() {
return (
<main className={styles.main}>
Hello Web3
<w3m-button/>
<Hydrate>
<TokenButton/>
</Hydrate>
</main>
)
}

We can now run our application and start minting our token! 🎉

I hope this article was helpful and do not hesitate to leave a comment if you have any questions. Thanks!
You can find the app that we have just built in the following link:
https://github.com/glitch-txs/simple-web3-app

--

--