Setup a Dapp easily with Wagmi, Viem and Web3Modal

As a Dapp developer, we always need to focus on connecting user wallet, getting user blockchain data (balance, block, hash,…) and of course calling smart contract functions. With these 3 tools your life will be much more easier. And your code base will be more elegant. Let take a look at these tools then build a simple Dapp with them. You can then use this project to build any kind of Dapp you need.

1. What’s wagmi, viem, web3Modal ?

  • https://wagmi.sh/
  • https://viem.sh/
  • https://web3modal.com/
  • Viem you can consider it as ethers library (https://docs.ethers.org/v5/). A library provides utilities for working with ethereum blockchain (connect smart contract, working with big number, working with rpc url,…)
  • Wagmi which using viem as it dependency , is a collection of React Hooks containing everything you need to start working with Ethereum. wagmi makes it easy to “Connect Wallet,” display ENS and balance information, sign messages, interact with contracts, and much more — all with caching, request deduplication, and persistence.
  • Web3Modal: a toolkit help us to connect to cross-platform blockchain wallet more easier

Ok let’s get start….

2. But first, a little story

Before ethers, developer used to use web3js, an old school library which provided ability to work with blockchain. But the framework itself lack a lot of features for example it not support promise it use call back metrics for handling the asynchonous task it make the code really hard to read. After that, ethers came out, with a lot of nice features and promise base actions which make working with blockchain is much more easier. Then one day, my colleague tell me to check the wagmi library which provides a lot of react hooks to work with smart contract, getting balance, etc… instead of using directly ethers lib. Using ethers i need to save some information about blockchain connection (ex: user address, user balance, current network, ether provider,…) to redux for using it later or create a class to abstract the calling smart contract functions steps. But with wagmi i just need to call the provided hooks and dont need to think much. Wagmi is an abstraction level of ethers and now is viem. It has a lot of nice things but also some sides that i don’t like that i will mention when we code.

For a short conclusion, nowadays, we have a lot of tools and lib for build an elegant Dapp with js/ts Just chose anything you want then start your project.

3. Ok let’s go

3.1 Create project

  • create a project using vite react and typescript
  • install these dependencies via yarn or npm
yarn add viem wagmi @wagmi/core @web3modal/ethereum @web3modal/react

3.2 create wagmi client and setup web3Modal

  • In this blog, i’ll chose the smart chain testnet for demo:
  • Network Name: Smart Chain – Testnet
  • New RPC URL: https://data-seed-prebsc-1-s1.binance.org:8545/
  • ChainID: 97
  • Symbol: BNB
  • Block Explorer URL: https://testnet.bscscan.com

First, we need a we need a wallet connect project id, visit: https://cloud.walletconnect.com/app create an account then create a project to get the project id

Now in App.tsx let setup wagmi and web3Modal to connect to the smart chain test net. At this step i highly recommend to read the three lib’s docs in section 1 of the blogs.

import { configureChains, createConfig, WagmiConfig } from "wagmi";
import { bscTestnet } from "wagmi/chains";
import {
  EthereumClient,
  w3mConnectors,
  w3mProvider,
} from "@web3modal/ethereum";
import { Web3Modal } from "@web3modal/react";
import Dapp from "./Dapp";

const chains = [bscTestnet];
const projectId = "your-walletconnect-project-id";
const { publicClient } = configureChains(chains, [w3mProvider({ projectId })]);
const wagmiConfig = createConfig({
  autoConnect: false,
  connectors: w3mConnectors({ projectId, chains }),
  publicClient,
});
const ethereumClient = new EthereumClient(wagmiConfig, chains);
function App() {
  return (
    <>
      <WagmiConfig config={wagmiConfig}>
        <Dapp />
      </WagmiConfig>

      <Web3Modal
        projectId={projectId}
        ethereumClient={ethereumClient}
        defaultChain={bscTestnet}
      />
    </>
  );
}

export default App;

We get the bscTestNet config from wagmi pass it the config object along with the provider and client of the ethereum from web3Modal. Then in Jsx we wrap all of our component inside <WagmiConfig> component so all children components can use wagmi hook then init an web3Modal component at the end of the page.

3.3 Connect disconnect

  • Let’s create a simple component named Dapp for our logic code. THIS COMPONENT MUST BE CHILDREN OF <WagmiConfig> Component.
import { useWeb3Modal } from "@web3modal/react";
import { useAccount, useDisconnect } from "wagmi";

const Dapp = () => {
  const { open } = useWeb3Modal();
  const { address, isConnected } = useAccount();
  const { disconnect } = useDisconnect();

  return (
    <div>
      <button onClick={isConnected ? disconnect : open}>
        {isConnected ? address : "Connect to wallet"}
      </button>
    </div>
  );
};

export default Dapp;

When you set up everything you just need to get the hook and use it really simple right ? Here is thhe result:

3.4 Calling the smart contract

Nice right now let’s try to call a smart contract. Let’s use this greet contract: https://testnet.bscscan.com/address/0xd24FcAedcc75dF6d9AE8581B9836e9781AE89fE8#writeContract

  • Try to get call the read function with useContractRead() hook. Like useQuery() from the react-querty this hook still return data, isLoading, isError state. I think wagmi use react query as it dependencies.
  • We also need smart contract abi, smart contract address you can get it from the contract link below. Here’s how we fetch the smart contract data
  const {data: currentGreet = "", isLoading} = useContractRead({
    abi: greeter_abi,
    address: "0xd24FcAedcc75dF6d9AE8581B9836e9781AE89fE8",
    functionName: "greet",
  })
  • Next we’ll make a transaction by calling a write method. According to the docs of wagmi, to call a contract we should use 2 hook one is usePrepareContractWrite() which take an object of smart contract abi, adress, function name we want to call,… as params then return for us a request object to pass it as params of second hook: the useContractWrite(). At this point i don’t really like wagmi when it comes to handle write contract. And you know what in some case we also need a transaction need to be done (confirmed on blockchain, like using ethers we need to call transaction.wait()). With wagmi to do that we need also invoke another hook called: useWaitForTransaction(). This is fuckin 3 hooks for just calling a smart contract function 😀

3.4 The @wagmi/core libarary

  • To solve the problem above, i’ve worked around that all wagmi hook actually call to the functions that defined in the @wagmi/core library. So instead of calling hook which with me at some points can make your code harder to read and manage (cause hook run when component render). We can use directly from @wagmi/core. Here’s how we call write method
  const [greeter, setGreeter] = useState("");
  
  const updateGreet = async () => {
    try {
      //prepare transaction
      const { request } = await prepareWriteContract({
        address: "0xd24FcAedcc75dF6d9AE8581B9836e9781AE89fE8",
        abi: greeter_abi,
        functionName: "setGreeting",
        args: ["Hello world"],
      });
      //send transaction to blockchain
      const { hash } = await writeContract(request);
      if (!hash) {
        throw new Error("Transaction failed");
      }
      //wait for transaction to be mined
      await waitForTransaction({ hash });
      //refetch data
      await reFetchGreeter();
    } catch (err: any) {
      console.log(err);
    }
  }; 
  /***/
  return (
  /**/
      <div>
        <input
          type="text"
          value={greeter}
          onChange={(e) => setGreeter(e.target.value)}
        />
        <button onClick={updateGreet}>Update greet</button>
      </div>
  /**/
  
  ) 
  • We still need three step prepare, send transaction then wait for it. But instead of calling 3 hooks now we just refer to the functions the code is much cleaner right?
  • All hooks from wagmi are actually using function from wagmi core. So you need to install both then base on use case to chose what to use. For example the useContractRead() hook will fetch smart contract data when component render(). But in some use case we only need to fetch smart contract data when some event occurs (ex: a button click,..). Then we need to call the readContract() function from wagmi/core https://wagmi.sh/core/actions/readContract

Here’s is a full Dapp component source code

import { useWeb3Modal } from "@web3modal/react";
import { useAccount, useContractRead, useDisconnect } from "wagmi";
import { greeter_abi } from "./geeting-abi";
import {
  prepareWriteContract,
  waitForTransaction,
  writeContract,
} from "@wagmi/core";
import { useState } from "react";

const Dapp = () => {
  const [greeter, setGreeter] = useState("");
  const { open } = useWeb3Modal();
  const { address, isConnected } = useAccount();
  const { disconnect } = useDisconnect();

  const { data: currentGreet = "", refetch: reFetchGreeter }: any =
    useContractRead({
      abi: greeter_abi,
      address: "0xd24FcAedcc75dF6d9AE8581B9836e9781AE89fE8",
      functionName: "greet",
    });

  const updateGreet = async () => {
    try {
      //prepare transaction
      const { request } = await prepareWriteContract({
        address: "0xd24FcAedcc75dF6d9AE8581B9836e9781AE89fE8",
        abi: greeter_abi,
        functionName: "setGreeting",
        args: [greeter],
      });
      //send transaction to blockchain
      const { hash } = await writeContract(request);
      if (!hash) {
        throw new Error("Transaction failed");
      }
      //wait for transaction to be mined
      await waitForTransaction({ hash });
      //refetch data
      await reFetchGreeter();
      alert("Transaction successful");
    } catch (err: any) {
      console.log(err);
    }
  };

  return (
    <div>
      <button onClick={isConnected ? disconnect : open}>
        {isConnected ? address : "Connect to wallet"}
      </button>

      <div>Current greet is {currentGreet}</div>

      <div>
        <input
          type="text"
          value={greeter}
          onChange={(e) => setGreeter(e.target.value)}
        />
        <button onClick={updateGreet}>Update greet</button>
      </div>
    </div>
  );
};

export default Dapp;

4. Conclusion

With wagmi, viem the abstraction level compare to ethers or web3 is very high. That why they are very easy to use and power full. But base on your use case you need to chose which hooks, which functions to use. Thanks for reading and good luck when building your Dapp

__CodingCat 2023__

Leave a Reply

Your email address will not be published. Required fields are marked *