Background

Development

10 min read

Building on Base with CoinbaseWallet SDK and Wagmi

This article offers a guide on how to deploy a smart contract on Base Goerli and how to incorporate Coinbase Wallet SDK and Wagmi into a Next.js application.

Leonardo Simone Digiorgio

Leonardo Simone Digiorgio

14 Jul 2023

Illustration of Base and Wagmi

Introduction

Connecting applications to users and seamlessly connecting to Ethereum’s ecosystem is central to the ever-evolving decentralized applications.

This guide explores the powerful combination of Coinbase Wallet SDK and Wagmi, a collection of React Hooks, to simplify and enhance the development process.

Base is a secure, low-cost, developer-friendly Ethereum L2 built to bring the next billion users to web3. The Coinbase Wallet SDK, an open source software development kit (SDK), enables seamless integration with millions of related users.

Meanwhile, Wagmi provides a comprehensive set of React Hooks, which enable various Ethereum transactions such as wallet transactions, ENS and balance information exposure, message signing, contract transactions, and more.

This integration offers developers the convenience of caching, request deduplication, and persistence, empowering them to build on a solid foundation while expanding their reach in the Ethereum ecosystem.

This article offers a guide on how to deploy a smart contract on Base Goerli and how to incorporate Coinbase Wallet SDK and Wagmi into a Next.js application.

In this article will be use as example my BuyMeACoffee project

The Smart Contract

First, check out the smart contract used in this project.


  // SPDX-License-Identifier: UNLICENSED
  pragma solidity ^0.8.9;

  contract BuyMeACoffee {

    error EmptyValue (string name, string message);
    error InsufficientFunds(uint256 msgValue);

    // Event to emit when the Memo is created
    event NewMemo(
      address indexed from,
      uint256 timestamp,
      string name,
      string message
    );

    struct Memo {
      address from;
      uint256 timestamp;
      string name;
      string message;
    }

    // List of all memos received from friends
    Memo[] memos;

    // Address of contract deployer
    address payable owner;

    // Deploy logic
    constructor() {
      owner = payable(msg.sender);
    }

    /**
    * @dev buy a coffee from contract owner
    * @param _name name of the coffee buyer
    * @param _message a nice message from the coffee buyer
    */
    function buyCoffee(
      string memory _name,
      string memory _message
    ) public payable {
      if(msg.value < 0.001 ether) {
        revert InsufficientFunds({
          msgValue: msg.value
        });
      }
      if(bytes(_name).length == 0 || bytes(_message).length == 0) {
        revert EmptyValue({
          name:  _name,
          message: _message
        });
      }

      memos.push(Memo(msg.sender, block.timestamp, _name, _message));

      // Emit a log event when a new memo is created
      emit NewMemo(msg.sender, block.timestamp, _name, _message);
    }

    /**
    * @dev send the entire balance stored in this contract to the owner
    */
    function withdrawTips() public {
      require(owner.send(address(this).balance));
    }

    /**
    * @dev retrieve all the memos received and stored on the blockchain
    */
    function getMemos() public view returns (Memo[] memory) {
      return memos;
    }

  }

In short, the BuyMeACoffee contract allows users to buy a coffee by sending Ether to the contract owner along with a personalized message. It stores the received memos and allows the owner to withdraw the accumulated funds.

Using Remix

Remix is an online IDE that you can use to rapidly develop and deploy smart contracts.

Remix contains a simulation of a blockchain that you can use to rapidly deploy and test your contracts. This simulation only exists within your browser, so you can’t share it with others or use external tools or a front end to interact with it. However, you can also deploy to a variety of testnets from within Remix. Doing so will allow you to share your contract with others, at the cost of making it public.

First of all open Remix and create a new file called BuyMeACoffee.

Remix intro

Then copy the smart contract above (1), navigate to Solidity compile (2) and compile BuyMeACoffee.sol (3). Now you have access to the ABI which it’s useful later on when we are talking on the front-end part.

Remix intro 2

Then:

⚠️ Note: During this stage, it is possible to encounter connectivity issues with Coinbase Wallet. You may experience difficulties connecting, or even if you manage to connect, the wallet might display that it is not connected. If that happened, please copy and save your recovery phrase. Then, sign out and sign in again using your recovery phrase, and attempt the process once more.

Remix intro 3

In the end, if you see the screen displaying the network as 84531 (Base Gorli network) and you can identify your account, you can be assured that you are on the right track (6).

You can proceed with the deployment of your smart contract by clicking the “Deploy” button (7). Once you’ve done that, you should observe the following information displayed at the bottom (8).

Remix intro 4

Remix intro 5

Now you have:

Front End: Configuration of WalletConnect and Wagmi

Obtain Project ID To install Web3Modal and Wagmi, you need to obtain a Project ID from WalletConnect Cloud by signing in or signing up and creating (or using an existing) project. Once you have the Project ID, you can add it to your .env file and next.config.js file.

New Project

Click + New Project

Project ID

Copy the Project ID

Put the Project ID into .env


  WALLET_CONNECT_PROJECT_ID=<YOUR_PROJECT_ID>

Configure the next.config.js for get access to process.env.WALLET_CONNECT_PROJECT_ID in your code

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  env: {
    WALLET_CONNECT_PROJECT_ID: process.env.WALLET_CONNECT_PROJECT_ID,
  },
};

module.exports = nextConfig;

Add Packages

In your terminal, run the command below to add the needed packages:


  npm install @web3modal/ethereum @web3modal/react wagmi viem

Change address contract with your deployed address contract

In the utils/contractInfo.js file, you can modify the contractAddress variable with the address of your deployed contract. Additionally, if you have made any changes to the BuyMeACoffee.sol file, you need to copy the updated ABI from Remix and replace the existing BuyMeACoffeeCustomError.json file with the new ABI. However, if you haven’t made any changes to BuyMeACoffee.sol, you can continue using the current ABI.

Config Wagmi

Lastly, prepare the WagmiConfig component, which handles the configuration for all hooks with CoinbaseWalletConnector that supports connecting with Coinbase Wallet using the Coinbase Wallet SDK.

import { configureChains, createConfig, WagmiConfig } from "wagmi";
import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet";
import { publicProvider } from "wagmi/providers";
import { infuraProvider } from "wagmi/providers/infura";
import { baseGoerli } from "wagmi/chains";

if (!process.env.INFURA_ID) {
  throw new Error("You need to provide WALLET_CONNECT_PROJECT_ID env variable");
}

const chains = [baseGoerli];
const projectId = process.env.INFURA_ID;

const { publicClient } = configureChains(chains, [
  infuraProvider({ projectId }),
  publicProvider(),
]);
const wagmiConfig = createConfig({
  connectors: [
    new CoinbaseWalletConnector({
      chains,
      options: {
        appName: "BuyMeACoffee",
      },
    }),
  ],
  publicClient,
});

function MyApp({ Component, pageProps }) {
  return (
    <>
      <WagmiConfig config={wagmiConfig}>
        <Component {...pageProps} />
      </WagmiConfig>
    </>
  );
}

export default MyApp;

Wagmi

Wagmi 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.##

Wagmi Hooks used in the project BuyMeACoffee

useAccount and useDisconnect

Hook for accessing account data. In this scenario, we utilize the “isConnected” value to determine the user’s login status. If the user is logged in, the form enables them to purchase a coffee by sending Ether to the contract owner, accompanied by a personalized message.

useAccount

Main.js

useAccount

We use useConnect hook for connecting to account with connectors and useDisconnect hook for disconnecting the connected account.

Here we are using the Coinbase SDK connector for connecting wallet.

import { useConnect, useDisconnect } from "wagmi";
import { CoinbaseWalletConnector } from "wagmi/connectors/coinbaseWallet";

function Main() {
  // ... code ...

  const { address, isConnected } = useAccount();
  const { connect, isLoading } = useConnect({
    connector: new CoinbaseWalletConnector({
      options: {
        appName: "BuyMeACoffee",
      },
    }),
  });
  const { disconnect } = useDisconnect();

  return (
    // .. code ..
    <section className={styles.container_form}>
      {isConnected ? (
        <>
          <button className={styles.btn} onClick={() => disconnect?.()}>
            {`${address.slice(0, 5)}...${address.slice(-4)}`}
          </button>
        </>
      ) : (
        <>
          <button className={styles.btn} onClick={() => connect?.()}>
            {isLoading ? (
              <>
                <Loading text={"Loading..."} />
              </>
            ) : (
              "Connect your wallet"
            )}
          </button>
        </>
      )}
    </section>
  );
}

useNetwork and useSwitchNetwork

we used useNetwork hook for accessing network data, such as current connected chain (in this case sepolia) and useSwitchNetwork hook to switch network in case the user change the network (using useEffect).

network

Main.js

useContractRead

Hook for calling a read method on a Contract.

A “read-only” function (constant function) on a Solidity contract is denoted by a view or pure keyword. They can only read the state of the contract, and cannot make any changes to it. Since read-only methods do not change the state of the contract, they do not require any gas to be executed, and can be called by any user without the need to pay for gas.

useContractRead

Typical usage of useContractRead

Which address is the contract address, abi is the ABI of the contract (see below), functionName is the function view of the contract and lastly args (if necessary) are the arguments of the contract.

An Application Binary Interface (ABI) for a smart contract is like a mediator that helps the contract communicate and work together with external applications and other smart contracts in the Web3 environment.

In our scenario:

useContractRead

index.jsx

Here get the data that we rename memos through READ call method “getMemos” which retrieve all the memos received and stored on the blockchain.

The variable “isFetched” is initially set to false and becomes true once the READ operation is completed. We use “isFetched” as a conditional statement, and when it evaluates to true, the following code will be rendered.

isFetched

index.jsx

The refetch function (renamed refetchMemos) is a function that simply re-run the READ call method “getMemos” We will see refetchMemos later when on the section of useWaitForTransaction

usePrepareContractWrite and useContractWrite

The Prepare Hooks in Wagmi improve user experience by allowing asynchronous work to be performed ahead of time, before an event is initiated by the user.

This reduces potential delays and helps avoid UX related issues.

The Prepare Hooks generate the necessary parameters for their paired hooks, such as useSendTransaction or useContractWrite, which previously performed long-running asynchronous tasks using the ethers signer.sendTransaction function.

Slow TTOW (Time To Open Wallet)

Slow TTOW (Time To Open Wallet) is a problem caused by performing long-running asynchronous tasks between a user interaction and opening the wallet.

This can lead to a delay and confuse the user, especially under slow network conditions.

Here the link where you can find more details and a video demonstrating the difference between a slow TTOW and a fast TTOW.

Usage of usePrepareContractWrite and useContractWrite

The configuration needed for the hook useContractWrite is like useContractRead

usePrepareContractWrite

Main.js

Excluding the properties we already explained in the context of useContractRead (address, abi, and functionName), the remaining properties in the code are:

  1. Args: Arguments to pass to function call, if you remember the smart contract the function is:

smartContract

  1. Enabled is set to false to disable this query from automatically running and becomes true once the name and the message variables are NOT empty. Enabled property is used to avoid situation like this:

error

Error messages if it is not put the enabled property.
  1. Value; Value in wei sent with this transaction (msg.value in Solidity)

  2. onSuccess: It is a callback function that will be executed when the function call is successful. In this case, it logs a success message to the console, including the data parameter that contains any returned data from the successful function call.

You can also find the onSuccess callback in various other hooks provided by Wagmi. This callback function allows you to perform specific actions or manipulate data when a function or action succeeds. Using the onSuccess callback allows you to modify the hook’s behavior and respond accordingly.

After that, insert the config inside useContractWrite ​which is hook for calling a write method on a Contract (in this case the buyMeACoffee method).

A “write” function on a Solidity contract modifies the state of the blockchain. These types of functions require gas to be executed, and hence a Transaction is needed to be broadcast in order to change the state.

useContractWrite

Which { write: buyMeACoffee } can be { write: <name_of_the_function> }.

One of the return values of the hook is write, which is a function that invokes the function of the smart contract.

{ data: dataBuyMeACoffee } represent the Transaction Hash (if is successful) of the write function.

And to activate the write function, it can be placed inside a button or another function.

buttonWrite

useWaitForTransaction

Waits for the Transaction to be included on a Block (one confirmation), and then returns the Transaction Receipt. If the Transaction reverts, then the action will throw an error.

In this scenario, we utilize the useWaitForTransaction hook to update the memos displayed on the website. This is achieved by re-running the getMemos function from the useContractRead hook (when is successful).

useWaitTransaction

We use the enabled parameter of the useWaitForTransaction hook to wait for the transaction hash generated by the useContractWrite hook.

When the write function (in this case buyMeACoffee) is called and executed successfully, the useWaitForTransaction hook executes and waits for the transaction to be confirmed in a block.

Once confirmed, the refetchMemos function from the useContractRead hook is triggered to update the memos.

Conclusion

In summary, this article provides guidance on integrating Coinbase SDK and Wagmi into a Next.js application, using the BuyMeACoffee project as an example.

Wagmi provides a collection of React Hooks that facilitate Ethereum-related development tasks. These hooks provide important functionality such as wallet integration, display of ENS and balance information, message signing, and contract negotiation.

Understanding the capabilities of Base Goerli, Coinbase SDK and Wagmi, developers can leverage their capabilities to create simple and efficient Web3 applications. These tools help grow and sustain Web3 development, and empower developers to make the most of blockchain technology.

Development

Want to Be In-The-Know with Our Newest Projects?

Join our newsletter to be the first to know when we start building new projects and other updates from Ethereal.

We care about your data in our privacy policy.

Provezero dApp