DEV Community

Cover image for Integrating Wagmi V2 and Rainbowkit in NextJs : A Comprehensive Guide (Part 1)
Dan Mugisho M.
Dan Mugisho M.

Posted on • Edited on

Integrating Wagmi V2 and Rainbowkit in NextJs : A Comprehensive Guide (Part 1)

Welcome to the forefront of Web3/Frontend development, where we embark on a transformative journey of integrating Wagmi V2 alongside RainbowKit for seamless wallet connection and unlocking advanced features for a robust and feature-rich dApp.

In this comprehensive guide, we’ll explore the complex landscape of modern web development, empowering you to effortlessly implement and optimize wallet functionalities within your NextJs project.

Whether you’re transitioning from Wagmi V1 or venturing into Wagmi V2 for the first time, this article promises to equip you with the knowledge to elevate your user experiences and streamline wallet interactions.

In this Part 1, we tackle the essentials: establishing seamless wallet connections and fetching crucial user data like addresses, network details, balances, ENS names…

Create the project

npx create-next-app@latest my-project - typescript - eslint
Enter fullscreen mode Exit fullscreen mode

Add Wagmi to the project, install the required packages

yarn add wagmi viem@2.x @tanstack/react-query
Enter fullscreen mode Exit fullscreen mode
  • Viem is a TypeScript interface for Ethereum that performs blockchain operations.

  • TanStack Query is an async state manager that handles requests, caching, and more.

Install Rainbowkit

yarn add  @rainbow-me/rainbowkit
Enter fullscreen mode Exit fullscreen mode

Let’s create and export a new config using createConfig, in lib/config.ts

'use client';

import { http, createStorage, cookieStorage } from 'wagmi'
import { sepolia, bscTestnet, blastSepolia } from 'wagmi/chains'
import { Chain, getDefaultConfig } from '@rainbow-me/rainbowkit'

const projectId = ‘’;

const supportedChains: Chain[] = [sepolia, bscTestnet, blastSepolia];

export const config = getDefaultConfig({
   appName: 'WalletConnection‘,
   projectId,
   chains: supportedChains as any,
   ssr: true,
   storage: createStorage({
    storage: cookieStorage,
   }),
  transports: supportedChains.reduce((obj, chain) => ({ ...obj, [chain.id]: http() }), {})
 });
Enter fullscreen mode Exit fullscreen mode

In this example, Wagmi is configured to use the Sepolia, BscTestnet and blastSepolia chains, wrapped in supportedChains array.

Wagmi uses client-side storage for fast initial data, but this can cause issues with SSR frameworks like Next.js. Enabling the ssr property in Wagmi config fixes this by hydrating the data on the client after the initial render.

Here, we use cookieStorage to store Wagmi data in browser cookies. This allows persistence across page refreshes within the same browser.

The reduce function iterates through the supportedChains array. For each chain, it creates a key-value pair in the transports object ( [bscTestnet.id]: http() ). These transports handle making HTTP requests to the blockchain nodes for interactions and data retrieval.

Check out the createConfig docs for more configuration options.

Wrap App in Context Provider and RainbowKitProvider

  • Wrap the app in the WagmiProvider React Context Provider and pass the config you created earlier to the value property. Check out the WagmiProvider docs to learn more about React Context in Wagmi.

  • In Wagmi V2, you no longer need to pass chains to <RainbowKitProvider>.

  • Inside the WagmiProvider, wrap the app in a TanStack Query React Context Provider, e.g. QueryClientProvider, and pass a new QueryClient instance to the client property.

Since Next.js 14 restricts functions in Client Components and improve code organization, let’s create a dedicated component to manage the problematic data handling logic.

In app/providers.tsx, add :

"use client";

import { WagmiProvider } from "wagmi";
import { RainbowKitProvider, darkTheme } from "@rainbow-me/rainbowkit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import { config } from "@/lib/config";

const queryClient = new QueryClient();

type Props = {
  children: React.ReactNode;
};

export default function Providers({ children }: Props) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <RainbowKitProvider
          theme={darkTheme({
            accentColor: "#0E76FD",
            accentColorForeground: "white",
            borderRadius: "large",
            fontStack: "system",
            overlayBlur: "small",
          })}
        >
          {children}
        </RainbowKitProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

We are marking the component "use client" as it wraps libraries ( WagmiProvider, QueryClientProvider, RainbowKitProvider ) that rely on browser-specific functionalities.

Since it will be imported in layout.tsx ( a Server Component ), marking it "use client" ensures these libraries are only loaded and executed on the client-side (user’s browser) during hydration. This prevents unnecessary code execution on the server and improves initial page load performance.

Let’s now import Providers in layout.tsx

import "./globals.css";
import "@rainbow-me/rainbowkit/styles.css";

import { Inter } from "next/font/google";
import Providers from "./providers";

const inter = Inter({ subsets: ["latin"] });

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

To ensure Wagmi’s cookie data persists across page loads in our Next.js application, we’ll need to set up a hydration mechanism.

This involves extracting the cookie from the request headers within our server component (app/layout.tsx) and using cookieToInitialState to convert it into a suitable format.

Finally, we’ll pass this initial state to the WagmiProvider component located in a client component (app/providers.tsx) tagged with "use client". This process essentially "hydrates" the cookie data on the client-side, making it accessible throughout our dApp.

In app/layout.tsx, let’s add :

...
+ import { headers } from "next/headers";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  + const cookie = headers().get("cookie");

  return (
    <html lang="en">
      <body className={inter.className}>
        + <Providers cookie={cookie}>{children}</Providers>
      </body>
    </html>
  );
}

Enter fullscreen mode Exit fullscreen mode

And in app/providers.tsx, add :

"use client";

...
+ import { WagmiProvider, cookieToInitialState } from "wagmi";

type Props = {
  children: React.ReactNode;
  + cookie?: string | null;
};

+ export default function Providers({ children, cookie }: Props) {
  + const initialState = cookieToInitialState(config, cookie);
  return (
    + <WagmiProvider config={config} initialState={initialState}>
      ...
    </WagmiProvider>
  );
}

Enter fullscreen mode Exit fullscreen mode

Now that everything is set up, every component inside the Wagmi and TanStack Query Providers can use Wagmi React Hooks.

In app/components/connectButton.tsx, let's add :

"use client";

import { useEffect, useRef } from "react";
import {
  useConnectModal,
  useAccountModal,
  useChainModal,
} from "@rainbow-me/rainbowkit";
import { useAccount, useDisconnect } from "wagmi";
import { emojiAvatarForAddress } from "@/lib/emojiAvatarForAddress";

export const ConnectBtn = () => {
  const { isConnecting, address, isConnected, chain } = useAccount();
  const { color: backgroundColor, emoji } = emojiAvatarForAddress(
    address ?? ""
  );

  const { openConnectModal } = useConnectModal();
  const { openAccountModal } = useAccountModal();
  const { openChainModal } = useChainModal();
  const { disconnect } = useDisconnect();

  const isMounted = useRef(false);

  useEffect(() => {
    isMounted.current = true;
  }, []);

  if (!isConnected) {
    return (
      <button
        className="btn"
        onClick={async () => {
          // Disconnecting wallet first because sometimes when is connected but the user is not connected
          if (isConnected) {
            disconnect();
          }
          openConnectModal?.();
        }}
        disabled={isConnecting}
      >
        { isConnecting ? 'Connecting...' : 'Connect your wallet' }
      </button>
    );
  }

  if (isConnected && !chain) {
    return (
      <button className="btn" onClick={openChainModal}>
        Wrong network
      </button>
    );
  }

  return (
    <div className="max-w-5xl w-full flex items-center justify-between">
      <div
        className="flex justify-center items-center px-4 py-2 border border-neutral-700 bg-neutral-800/30 rounded-xl font-mono font-bold gap-x-2 cursor-pointer"
        onClick={async () => openAccountModal?.()}
      >
        <div
          role="button"
          tabIndex={1}
          className="h-8 w-8 rounded-full flex items-center justify-center flex-shrink-0 overflow-hidden"
          style={{
            backgroundColor,
            boxShadow: "0px 2px 2px 0px rgba(81, 98, 255, 0.20)",
          }}
        >
          {emoji}
        </div>
        <p>Account</p>
      </div>
      <button className="btn" onClick={openChainModal}>
        Switch Networks
      </button>
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode
  • useConnectModal, useAccountModal, useChainModal : Hooks from @rainbow-me/rainbowkit that trigger specific modals (popups) for connecting wallets, managing accounts, and selecting networks.

  • useAccount, useDisconnect : Hooks from wagmi that provide information about the connected account and a function to disconnect the wallet.

  • emojiAvatarForAddress : A custom function (likely located in lib/emojiAvatarForAddress.js, that you will find in this project repo) generates an emoji avatar based on the connected wallet address.

This component effectively manages the user’s wallet connection experience in our Next.js dApp by leveraging RainbowKit hooks and custom functions, providing a seamless flow for connecting, switching networks, and accessing account information.

In app/components/profile.tsx, add :

"use client";

import { useAccount, useBalance, useEnsName } from "wagmi";
import { middleEllipsis } from "@/lib/utils";
import { formatUnits } from "viem";

export default function Profile() {
  const { address, chain } = useAccount();

  const { data } = useBalance({
    address,
  });

  const ens = useEnsName({
    address,
  });

  return (
    <div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
      <div className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30">
        <h2 className="mb-3 text-2xl font-semibold">Wallet address</h2>
        <p className="m-0 w-[30ch] text-sm opacity-50">
          {middleEllipsis(address as string, 12) || ""}
        </p>
      </div>

      <div className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30">
        <h2 className={`mb-3 text-2xl font-semibold`}>Network</h2>
        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
          {chain?.name || ""}
        </p>
      </div>

      <div className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30">
        <h2 className={`mb-3 text-2xl font-semibold`}>Balance</h2>
        <div className={`m-0 max-w-[30ch] text-sm opacity-50`}>
          {data ? (
            <p>
              {Number(formatUnits(data.value, data.decimals)).toFixed(4)}{" "}
              {data.symbol}
            </p>
          ) : (
            <div />
          )}
        </div>
      </div>

      <div className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30">
        <h2 className={`mb-3 text-2xl font-semibold`}>EnsName</h2>
        <p className={`m-0 max-w-[30ch] text-sm opacity-50 text-balance`}>
          {ens.data || ""}
        </p>
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode
  • middleEllipsis function (defined in lib/utils): This custom function likely truncates long wallet addresses in the middle, displaying only the beginning and end portions for improved readability within the limited space. It takes the address string and a maximum character length as input.

  • formatUnits function from Viem library converts the raw balance value from the blockchain (often in wei for ETH) to a more user-friendly format with four decimal places.

By relying on Wagmi hooks (useAccount, useBalance, and useEnsName) and custom utility functions, this component effectively retrieves and displays user’s wallet address, network name (if available), balance (once fetched), and ENS name (if any). This provides users with a clear overview of their connected wallet details within our dApp.

Let’s now import ConnectBtn and Profile in page.tsx :

"use client";

import Image from "next/image";
import { ConnectBtn } from "./components/connectButton";
import Profile from "./components/profile";

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
        <ConnectBtn />
      </div>

      <div className="relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
        <Image
          className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
          src="/next.svg"
          alt="Next.js Logo"
          width={180}
          height={37}
          priority
        />
      </div>

      <Profile />
    </main>
  );
}

Enter fullscreen mode Exit fullscreen mode

Since middleEllipsis and emojiAvatarForAddress functions are not provided here, kindly retrieve them in the article’s repo on github.

This guide has empowered you to establish seamless wallet connections and fetch essential user data in your dApp. But the journey doesn’t end here! Stay tuned for Part 2, where we’ll delve into advanced features, refining your Web3/Frontend development skills and taking your dApp to the next level!

☞ Follow me on Twitter & Linkedin

☞ Kindly subscribe for my upcoming articles

Top comments (0)