Wagmi + ReactJS Example: Transfer Crypto and Mint an NFT

In this tutorial, we’ll learn how to build a Web3 Dapp (Decentralized Application) that connects to your crypto wallet, allowing you to transfer funds and mint NFTs. We’ll be using Next.js, a popular React framework, and Wagmi.sh, a collection of React Hooks that easily integrates your wallet into your website.

Here’s an outline of the tutorial:

  • Getting Started
  • Part 1: Transferring Crypto with React + Wagmi
  • Part 2: Minting an NFT with React + Wagmi

Authorship

This article was co-authored by Aymeric Taylor (LinkedIn, Twitter), a research intern at RareSkills.

Getting Started

For the sake of simplicity, we’ll be using “Polygon Mumbai” as our test network for this tutorial, It is also supported on OpenSea.

Connect your Wallet to Polygon Mumbai – MetaMask

Navigate through MetaMask → Settings → Advanced and make sure you allow Show test networks.

metamask test networks

Next, click on the Network Selection on the top right section and select Add network

metamask add network

If Polygon Mumbai isn’t already available to select from, you can follow the steps below.

Select Add a network manually and input the following:

Network Name: Matic Mumbai

New RPC URL : https://rpc-mumbai.maticvigil.com/

Chain ID : 80001

Currency Symbol : MATIC

Block explorer URL (optional) : https://mumbai.polygonscan.com/

Now simply switch onto the Polygon Network

Wagmi Examples Part 1: Transferring Ether with React + Wagmi

Step 1: Set up website with node js

You’ll need to have nodejs installed for this.

First create your Next.js project with

npx create-next-app@latest myapp

Check the Typescript and ESLint option using the arrows and enter key. It should look something like this

create react app typescript

Open your project in vscode and install wagmi.sh and use-debounce package (we’ll get into that later).

npm i wagmi ethers@^5
npm i use-debounce --save

Wagmi is basically a set of React Hooks that simplifies Ethereum development by providing useful features such as connecting wallets and interacting with contracts which we’ll learn in this tutorial. More on wagmi.sh.

Step 2: Use configureChains to pick the network to connect to

Head over to pages/_app.tsx and add in the following code. Each of functionalities are explained in the comment sections.

import "@/styles/globals.css"; // CSS doesnt really matter now
import type { AppProps } from "next/app";
import { WagmiConfig, configureChains, createClient, mainnet } from "wagmi";
import { publicProvider } from "wagmi/providers/public";
import { polygonMumbai } from "wagmi/chains";
import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet'
import { InjectedConnector } from 'wagmi/connectors/injected'
import { MetaMaskConnector } from 'wagmi/connectors/metaMask'
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect'

// configure the chains and provider that you want to use for your app, 
// keep in mind that you're allowed to pass any EVM-compatible chain.
// It is also encouraged that you pass both alchemyProvider and infuraProvider.
const { chains, provider, webSocketProvider } = configureChains(
  [mainnet, polygonMumbai],
  [publicProvider()]
);

// This creates a wagmi client instance of createClient 
// and passes in the provider and webSocketProvider.

const client = createClient({
  autoConnect: false,
  provider,
  webSocketProvider,
  connectors: [ // connectors is to connect your wallet, defaults to InjectedConnector();
    new MetaMaskConnector({ chains }),
    new CoinbaseWalletConnector({
      chains,
      options: {
        appName: 'wagmi',
      },
    }),
    new WalletConnectConnector({
      chains,
      options: {
        projectId: '...',
      },
    }),
    new InjectedConnector({
      chains,
      options: {
        name: 'Injected',
        shimDisconnect: true,
      },
    }),
  ],
});

export default function App({ Component, pageProps }: AppProps) {
  return (
    // Wrap your application with the WagmiConfig component 
    // and pass the client instance as a prop to it.
    <WagmiConfig client={client}>
      <Component {...pageProps} />
    </WagmiConfig>
  );
}

We now have our networks configured, our next step is to allow users to choose which wallet to connect to. As you can see above, we have 4 wallet connectors set up. MetaMask, WalletConnect, Coinbase and Injected(which again is basically your default wallet).

Step 3: Use useConnect to enable picking the browser wallet

On pages/index.tsx copy and paste the following code. Make sure you add the css, so it looks good!

import { useAccount, useConnect } from "wagmi";
import { useEffect } from "react";
import styles from "@/styles/Home.module.css";

export default function Home() {
  const { connect, connectors } = useConnect();
  const { isConnected } = useAccount();

  useEffect(() => {
    console.log(
      `Current connection status: ${isConnected ? "connected" : "disconnected"}`
    );
  }, [isConnected]);

  return (
    <>
      <p
        className={styles.status}
        style={{
          color: isConnected ? "green" : "red",
        }}
      >
        {" "}
        {isConnected !== undefined
          ? isConnected
            ? "Connected"
            : "Disconnected"
          : "loading..."}
      </p>
      <div className={styles.maincontainer}>
        <div className={styles.container}>
          <div className={styles.buttonswrapper}>
            <div className={styles.buttonswrapperGrid}>
              {connectors.map((connector) => (
                <button
                  suppressHydrationWarning
                  key={connector.id}
                  onClick={() => connect({ connector })}
                  className={styles.button28}
                >
                  {connector.name}
                </button>
              ))}
            </div>
          </div>
        </div>

        {/* send funds */}

        {/* mint nft */}
      </div>
    </>
  );
}

Step 4: Add css to make it look nice

Delete everything on styles/globals.css and styles/Home.module.css.

Copy paste the css code below on styles/globals.css.

body {
    height: 100vh;     
    background: rgb(11,3,48); /* For browsers that do not support gradients */     
    background: linear-gradient(to bottom right,#0b0330, #5904a4);   
    font-family: 'Inter Medium', sans-serif;
}

Copy paste the css code below into styles/Home.module.css.

.status {
  text-align: left;
  margin: 0px;
  font-family: "Inter Medium", sans-serif;
}

.maincontainer {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  height: 60%;
  width: 60%;

}

.buttonswrapperGrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: repeat(2, 1fr);
  grid-gap: 1.5rem;
  justify-items: center;
  align-items: center;
  justify-content: center;
  margin-bottom: 40px;
  padding: 20px;
}

/* CSS */
.button28 {
  align-items: center;
  background-color: #da5fff;
  border: 2px solid #1d0321;
  border-radius: 8px;
  box-sizing: border-box;
  color: #111;
  cursor: pointer;
  display: flex;
  font-family: Inter, sans-serif;
  font-size: 16px;
  height: 55px;
  justify-content: center;
  line-height: 24px;
  max-width: 75%;
  padding: 0 25px;
  position: relative;
  text-align: center;
  text-decoration: none;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  width: 100%;
}

.button28:after {
  background-color: #210c20;
  border-radius: 8px;
  content: "";
  display: block;
  height: 48px;
  left: 0;
  width: 100%;
  position: absolute;
  top: -2px;
  transform: translate(8px, 14px);
  transition: transform 0.2s ease-out;
  z-index: -1;
}

.button28:hover:after {
  transform: translate(0, 0);
}

.button28:active {
  background-color: #ffdeda;
  outline: 0;
}

.button28:hover {
  outline: 0;
}

@media (min-width: 768px) {
  .button28 {
    padding: 0 40px;
  }
}

.myinput {
  background-color: rgb(253, 232, 255);
  border-radius: 15px;
  border: 2px solid #1d0321;
  padding: 20px;
  width: 44%;
  height: 15px;
}

.inputcontainer {
  display: flex;
  justify-content: space-between; /* Space the input fields evenly */
  align-items: center; /* Align the input fields vertically in the container */
}

.buttoncontainer {
  display: flex;
  justify-content: center; /* Center the button horizontally */
  align-items: center; /* Center the button vertically */
  margin-top: 16px;
  margin-bottom: 16px;
}
.mintcontainer {
  display: flex;
  justify-content: center; /* Center the button horizontally */
  align-items: center; /* Center the button vertically */
  margin-top: 50px;
  margin-bottom: 16px;
}
/* CSS */
.button64 {
  align-items: center;
  background-image: linear-gradient(144deg, #af40ff, #280b36 50%, #e30eff);
  border: 0;
  border-radius: 8px;
  box-shadow: rgba(151, 65, 252, 0.2) 0 15px 30px -5px;
  box-sizing: border-box;
  color: #ffffff;
  display: flex;
  font-family: Phantomsans, sans-serif;
  font-size: 20px;
  justify-content: center;
  line-height: 2em;
  max-width: 100%;
  min-width: 140px;
  padding: 3px;
  text-decoration: none;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  white-space: nowrap;
  cursor: pointer;
}

.button64:active,
.button64:hover {
  outline: 0;
}

.button64 span {
  background-color: rgb(5, 6, 45);
  padding: 16px 24px;
  border-radius: 6px;
  width: 100%;
  height: 100%;
  transition: 300ms;
}

.button64:hover span {
  background: none;
}

@media (min-width: 768px) {
  .button64 {
    font-size: 24px;
    min-width: 196px;
  }
}

/* CSS */
.button49,
.button49:after {
  width: 150px;
  height: 76px;
  line-height: 78px;
  font-size: 20px;
  font-family: 'Bebas Neue', sans-serif;
  background: linear-gradient(45deg, transparent 5%, #ff01ee 5%);
  border: 0;
  color: #fff;
  letter-spacing: 3px;
  box-shadow: 6px 0px 0px #00E6F6;
  outline: transparent;
  position: relative;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}

.button49:after {
  --slice-0: inset(50% 50% 50% 50%);
  --slice-1: inset(80% -6px 0 0);
  --slice-2: inset(50% -6px 30% 0);
  --slice-3: inset(10% -6px 85% 0);
  --slice-4: inset(40% -6px 43% 0);
  --slice-5: inset(80% -6px 5% 0);

  content: 'GET YOUR NFT';
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(45deg, transparent 3%, #00E6F6 3%, #00E6F6 5%, #c401ff 5%);
  text-shadow: -3px -3px 0px #F8F005, 3px 3px 0px #00E6F6;
  clip-path: var(--slice-0);
}

.button49:hover:after {
  animation: 1s glitch;
  animation-timing-function: steps(2, end);
}

@keyframes glitch {
  0% {
    clip-path: var(--slice-1);
    transform: translate(-20px, -10px);
  }
  10% {
    clip-path: var(--slice-3);
    transform: translate(10px, 10px);
  }
  20% {
    clip-path: var(--slice-1);
    transform: translate(-10px, 10px);
  }
  30% {
    clip-path: var(--slice-3);
    transform: translate(0px, 5px);
  }
  40% {
    clip-path: var(--slice-2);
    transform: translate(-5px, 0px);
  }
  50% {
    clip-path: var(--slice-3);
    transform: translate(5px, 0px);
  }
  60% {
    clip-path: var(--slice-4);
    transform: translate(5px, 10px);
  }
  70% {
    clip-path: var(--slice-2);
    transform: translate(-10px, 10px);
  }
  80% {
    clip-path: var(--slice-5);
    transform: translate(20px, -10px);
  }
  90% {
    clip-path: var(--slice-1);
    transform: translate(-10px, 0px);
  }
  100% {
    clip-path: var(--slice-1);
    transform: translate(0);
  }
}

@media (min-width: 768px) {
  .button49,
  .button49:after {
    width: 200px;
    height: 86px;
    line-height: 88px;
  }
}

Step 5: Run the website and test it

When you run this, this should initiate a connection to your wallet. Once you’ve approved the connection, it should look something like this:

npm run dev

website preview

Step 6: Add ability to transfer cryptocurrency

Now that we’ve connected our wallet, we can transfer funds from our wallet to others. Make sure you have some Mumbai MATIC in your wallet. You can get some here https://faucet.polygon.technology

We’ll now be creating the input fields and send button for the transaction. Create a new file under pages/ directory and name it RareSend.tsx. Copy paste the code below. The explanations are in the code comments.

import { parseEther } from "ethers/lib/utils.js";
import React, { useState } from "react";
import { useDebounce } from "use-debounce";
import { usePrepareSendTransaction, useSendTransaction } from "wagmi";
import styles from "@/styles/Home.module.css";

// what this does is simply disable the SendFunds function if the value passed is false
interface SendFundsProps {
  disabled?: boolean;
}

export default function SendFunds(props: SendFundsProps) {
  // declare two state variables for the recipient and the amount
  const [to, setTo] = useState("");
  const [debouncedTo] = useDebounce(to, 500); // useDebounce() hook to debounce the recipient input

  const [amount, setAmount] = useState("");
  const [debouncedAmount] = useDebounce(amount, 500); // useDebounce() hook to debounce the amount input

  // use the usePrepareSendTransaction() hook to prepare a transaction request
  const { config } = usePrepareSendTransaction({
    request: {
      to: debouncedTo, // recipient from debounced input
      value: debouncedAmount ? parseEther(debouncedAmount) : undefined, // amount from debounced input
    },
  });

  // use the useSendTransaction() hook to create a transaction and send it
  const { sendTransaction } = useSendTransaction(config);

  return (
    <>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          sendTransaction?.(); // if sendTransaction is defined, execute it
        }}
      >
        <div className={styles.inputcontainer}>
          <input
            aria-label="Recipient"
            onChange={(e) => setTo(e.target.value)} // update the recipient state on input change
            placeholder="Address destination"
            value={to}
            className={styles.myinput}
          />
          <input
            aria-label="Amount (ether)"
            onChange={(e) => setAmount(e.target.value)} // update the amount state on input change
            placeholder="enter amount"
            value={amount}
            className={styles.myinput}
          />
        </div>
        <div className={styles.buttoncontainer}>
          <button
            disabled={!sendTransaction || !to || !amount}
            className={styles.button64}
          >
            Send
          </button>{" "}
          {/* disable the button if required fields are empty */}
        </div>
      </form>
    </>
  );
}

useDebounce() explained

To avoid overloading the RPC and getting rate-limited, we’ll limit the use of usePrepareContractWrite hook, which requests gas estimates on component mount and args modification. We use useDebounce hook in the component to delay updating the token ID by 500ms if no changes have been made.

Step 7: Insert sendFunds component

Next, simply add the like this in pages/index.tsx.

import { useAccount, useConnect } from "wagmi";
import SendFunds from "./RareSend";
import { useEffect } from "react";
import styles from "@/styles/Home.module.css";

export default function Home() {
  {
    /* some code... */
  }

  return (
    <>
      <div>
        {/* some code... */}

        {/* send funds */}
        <SendFunds disabled={!isConnected} />

        {/* mint nft */}
      </div>
    </>
  );
}

As long as you have Matic in your address, you’re able send it to another account!

This should be how your website looks like:

react js send cryptocurrency

Congratulations on reaching this point! You have successfully created your own website that can send funds from your wallet to another account. You are now able to easily transfer cryptocurrency between accounts, without having to rely on a third-party service or manually entering transaction details. Well done! It’s that easy!

Wagmi Examples Part 2: Minting an NFT with React + Wagmi

We assume you have already have deployed an NFT smart contract to the blockchain. You can follow this video tutorial to do so: https://youtu.be/LIoFbudNVZs.

NFTs of AI generated cats

NFTs of AI generated cats

The mint function can be created as follows. Create a mint.tsx and add the codes below.

// Initialize ethers.js and wagmi dependencies
import { ethers } from "ethers";
import * as React from "react";
import {
  usePrepareContractWrite,
  useContractWrite,
  useWaitForTransaction,
} from "wagmi";
import styles from "@/styles/Home.module.css";

// Define a React function component to mint an NFT
export function MintNFT() {
  // Prepare the contract write configuration by providing the contract's address, ABI, function name, and overrides
  const { config } = usePrepareContractWrite({
    address: "Your Contract Address",
    abi: [
      {
        name: "mint",
        type: "function",
        stateMutability: "payable",
        inputs: [],
        outputs: [],
      },
    ],
    functionName: "mint",
    overrides: {
      from: "Your Walllet Address",
      value: ethers.utils.parseEther("0.000000001"), //the integer value should match your nft minting requirements
    },
  });

  // Use the useContractWrite hook to write to the contract's mint function and obtain the transaction data and write function
  const { data, write } = useContractWrite(config);

  // Use the useWaitForTransaction hook to wait for the transaction to be mined and return loading and success states
  const { isLoading, isSuccess } = useWaitForTransaction({
    hash: data?.hash,
  });

  // Render the component with a button that triggers the mint transaction when clicked, a loading message while the transaction is in progress, and a success message when the transaction is successful
  return (
    <div className={styles.mintcontainer}>
      <button
        disabled={!write || isLoading}
        onClick={() => write?.()}
        className={styles.button49}
      >
        {isLoading ? "Minting..." : "Mint"}
      </button>
      {isSuccess && (
        <div>
          Successfully minted your NFT!
          <div>
            <a href={`https://etherscan.io/tx/${data?.hash}`}>Etherscan</a>
          </div>
        </div>
      )}
    </div>
  );
}

Now you can simply insert it into the index.tsx file like this:

import { useAccount, useConnect } from "wagmi";
import SendFunds from "./RareSend";
import { useEffect } from "react";
import styles from "@/styles/Home.module.css";
import { MintNFT } from "./mint";

export default function Home() {
  const { connect, connectors } = useConnect();
  const { isConnected } = useAccount();

  useEffect(() => {
    console.log(
      `Current connection status: ${isConnected ? "connected" : "disconnected"}`
    );
  }, [isConnected]);

  return (
    <>
      <p
        className={styles.status}
        style={{
          color: isConnected ? "green" : "red",
        }}
      >
        {" "}
        {isConnected !== undefined
          ? isConnected
            ? "Connected"
            : "Disconnected"
          : "loading..."}
      </p>
      <div className={styles.maincontainer}>
        <div className={styles.container}>
          <div className={styles.buttonswrapper}>
            <div className={styles.buttonswrapperGrid}>
              {connectors.map((connector) => (
                <button
                  suppressHydrationWarning
                  key={connector.id}
                  onClick={() => connect({ connector })}
                  className={styles.button28}
                >
                  {connector.name}
                </button>
              ))}
            </div>
          </div>
        </div>

        {/* send funds */}
        <SendFunds disabled={!isConnected} />

        {/* mint nft */}
        <MintNFT />
      </div>
    </>
  );
}

Your final website should look something like this:

wagmi react js mint nft transfer crypto

Congratulations! You’ve just made your own Decentralized Application that is capable of Sending Transactions and Minting an NFT.

Originally Published April 24, 2023

Viem React Js Example: Transfer, Mint, and View Blockchain State

Viem React Js Example: Transfer, Mint, and View Blockchain State In this tutorial, we’ll build a fully functional Dapp with the Viem typescript library + React (Next.js). We’ll cover the necessary steps to connect your wallet, transfer crypto, interact with smart contracts (eg.g mint NFTs) and query the blockchain. Viem is a Typescript alternative to […]

ZK-addition-dapp with Noir and Nextjs

ZK-addition-dapp with Noir and Nextjs We will demonstrate a step-by-step exploration of a basic zk-dapp designed for verifying additions. This application enables users to prove that the sum of two numbers, X and Y, equals Z without disclosing the actual numbers on the blockchain. Although solving this problem does not necessarily demand zero-knowledge proofs, we […]

Web3.js Example. Latest version 4.x. Transfer, Mint and Query the Blockchain

Web3.js Example. Latest version 4.x. Transfer, Mint and Query the Blockchain The newest version of web3.js, 4.x, has just been unveiled. In this guide, we’ll delve into integrating web3.js into HTML to Transfer, Mint NFTs as well as querying the Blockchain. Several features within the web3.js v 1.10.0 documentation are set to be deprecated, therefore […]