How to Implement MetaMask SDK with Nextjs

Learn how to integrate a 'Connect Wallet' button with MetaMask in Next.js. This tutorial covers setup, MetaMask SDK usage, and UI creation with ShadcnUI for blockchain interaction.

by Sushmita RashidNovember 15, 2023
How to Implement MetaMask SDK with Nextjs Image

In web3, a wallet refers to a digital application that allows users to interact with blockchain networks. Wallets serve as the bridge between users and the decentralized world of blockchain. Unlike traditional web applications that use email and password combinations or OAuth services for authentication, web3 apps authenticate users based on the ownership of a wallet and its associated private keys. The "Connect Wallet" button initiates this authentication process.

In this tutorial, we will implement a “Connect Wallet” button in a Next.js app that initiates the authentication process with MetaMask wallet.

Connect wallet image

Prerequisites:


  • Familiarity with Next.js and React.
  • Node.js and npm/yarn installed.
  • MetaMask extension installed in your browser.

While these are some prerequisites for this tutorial, the best way to learn is by getting started and learning along the way.

Setting up the Next.js project:


To set up a Next.js project, you need to have Node.js installed on your machine. Once Node.js is installed, you can create a new Next.js application by opening your terminal and running the following command:

npx create-next-app@latest

We will see the following prompts:

npx create-next-app@latest
✔ What is your project named? … next-metamask-template
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes

This command creates a new folder with the specified project name, sets up the initial Next.js structure, and installs all the necessary dependencies. Navigate to your folder.

cd next-metamask-template

and run the following command npm run dev, this command starts the Next.js application in development mode, and you can view your application by going to http://localhost:3000 in your web browser.

Creating the user interface


We will be adding a component library to our app. Let’s go ahead and install ShadcnUI.

npx shadcn-ui@latest init

We will see the following prompts to configure components.json:

Would you like to use TypeScript (recommended)? no / yes
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Where is your global CSS file? › › app/globals.css
Do you want to use CSS variables for colors? › no / yes
Where is your tailwind.config.js located? › tailwind.config.js
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils
Are you using React Server Components? › no / yes

From the ShadcnUI component library, we will be using two components for this tutorial: Button and Popover.

We will also be using @metamask/sdk-react, you can read more about it from the MetaMask documentation.

Let’s Install the SDK:

npm i @metamask/sdk-react

We will be creating a new component called NavBar.tsx and adding the “Connect Wallet” button and SDK. Our component should look like this at first:

"use client";

import Link from "next/link";

import WalletIcon from "../public/icons/WalletIcon";

import { Button } from "./ui/button";

import { useSDK, MetaMaskProvider } from "@metamask/sdk-react";
import { formatAddress } from "../lib/utils";
import {
  Popover,
  PopoverTrigger,
  PopoverContent,
} from "@/components/ui/popover";

export const ConnectWalletButton = () => {
  const { sdk, connected, connecting, account } = useSDK();

  const connect = async () => {
    try {
      await sdk?.connect();
    } catch (err) {
      console.warn(`No accounts found`, err);
    }
  };

  const disconnect = () => {
    if (sdk) {
      sdk.terminate();
    }
  };

  return (
    <div className="relative">
      {connected ? (
        <Popover>
          <PopoverTrigger>
            <Button>{formatAddress(account)}</Button>
          </PopoverTrigger>
          <PopoverContent className="mt-2 w-44 bg-gray-100 border rounded-md shadow-lg right-0 z-10 top-10">
            <button
              onClick={disconnect}
              className="block w-full pl-2 pr-4 py-2 text-left text-[#F05252] hover:bg-gray-200"
            >
              Disconnect
            </button>
          </PopoverContent>
        </Popover>
      ) : (
        <Button disabled={connecting} onClick={connect}>
          <WalletIcon className="mr-2 h-4 w-4" /> Connect Wallet
        </Button>
      )}
    </div>
  );
};

Let’s see what we did here:

1. useSDK hook: Initially, our focus is on the useSDK hook, which allows access to the MetaMask SDK's functionalities. This hook provides an SDK object for interacting with MetaMask, a connected boolean to verify if the wallet is connected, a connecting boolean to check the connection status, and an account for accessing the user’s account address.

2. Connect and disconnect functions: The setup includes two key functions: connect and disconnect. The connect function is an asynchronous operation that tries to establish a connection with the MetaMask wallet using the SDK. Upon a successful connection, it updates the connected and account values from the useSDK hook. The disconnect function, on the other hand, is used to disconnect the wallet by invoking sdk.terminate().

3. UI elements: Our implementation incorporates two UI elements – a button and a popover. The component's rendering changes based on the connection status. When connected, it displays a Popover component showing the formatted account address and an option to disconnect. If not connected, it shows a "Connect Wallet" button, which becomes disabled during an ongoing connection attempt.

Now Let’s add the code for the NavBar:

export const NavBar = () => {
  const host =
    typeof window !== "undefined" ? window.location.host : "defaultHost";

  const sdkOptions = {
    logging: { developerMode: false },
    checkInstallationImmediately: false,
    dappMetadata: {
      name: "Next-Metamask-Boilerplate",
      url: host, // using the host constant defined above
    },
  };

  return (
    <nav className="flex items-center justify-between max-w-screen-xl px-6 mx-auto py-7 rounded-xl">
      <Link href="/" className="flex gap-1 px-6">
        <span className="hidden text-2xl font-bold sm:block">
          <span className="text-gray-900">Template</span>
        </span>
      </Link>
      <div className="flex gap-4 px-6">
        <MetaMaskProvider debug={false} sdkOptions={sdkOptions}>
          <ConnectWalletButton />
        </MetaMaskProvider>
      </div>
    </nav>
  );
};

export default NavBar;

Let’s see what we did over here:

  1. Host variable: This variable determines the current window's host address for use in the SDK options. It includes a conditional check to verify if it's running in a browser environment.

  2. SDK options: These are the configuration options for the MetaMask SDK, setting preferences for logging and installation checks, and including metadata about the dapp.

  3. NavBar component: The NavBar features a link to the homepage and incorporates the ConnectWalletButton component. To provide the necessary context from the MetaMask SDK, it wraps the ConnectWalletButton with the MetaMaskProvider component, which is imported from @metamask/sdk-react.

  • Wallet Icon: To obtain the wallet icon, we used SVGR, a tool that converts SVG images into React components.

To prevent the full wallet address from occupying too much space in the NavBar, we'll make adjustments in the utils.ts file within the lib folder. This ensures the address is displayed in a more concise format.

import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Add the following lines:

export const formatBalance = (rawBalance: string) => {
  const balance = (parseInt(rawBalance) / 1000000000000000000).toFixed(2);
  return balance;
};

export const formatChainAsNum = (chainIdHex: string) => {
  const chainIdNum = parseInt(chainIdHex);
  return chainIdNum;
};

export const formatAddress = (addr: string | undefined) => {
  return `${addr?.substring(0, 8)}...`;
};

The above functions - formatBalance, formatChainAsNum, and formatAddress are designed to be imported and used throughout a dApp to consistently format and display blockchain data to the users.

On the NavBar Component we will import it like this:

import { formatAddress } from "../lib/utils";

And we used it here on the button:

<Button>{formatAddress(account)}</Button>

Now all that is left is to import NavBar.tsx into our layout.tsx component.

If we run our development server we will see the changes we have made to our app.

Connect and disconnect button

Conclusion


Metamask SDK is truly convenient and a game changer when it comes to building DApps. It enables users to easily connect to the MetaMask browser extension and MetaMask Mobile.

This was an intro to how to get started with MetaMask SDK but we can do a lot more with this and can use it on fullstack applications as well. You can find the code for this project on this repository here

I also reccommend checking the following resources:

Happy Building. 🚀

Receive our Newsletter