Integrate Multiple Wallets into Your dApp in 15 Minutes

Cover Image for Integrate Multiple Wallets into Your dApp in 15 Minutes
Zach Rosen, Co-Founder
Zach Rosen, Co-Founder

A step by step guide for easily adding a Connect Wallet component to your project

MetaMask is the dominant Ethereum wallet. So, most devs skip integrating other wallets. Buuuut, integrating other wallets takes literally 15 minutes and enables more folks to use your dApp. Worth it? I think so :)

Let's get this deployed in 15 minutes.

Screen Shot 2022-04-01 at 9.58.08 AM

Who this is for:

This write up is for dApp developers as well as folks just getting into blockchain development. I’ll assume that you’re familiar with Javascript and ReactJS, understand the basic workings of a command line(CLI), and understand the package manager Yarn.

What this covers:

In this write up I’ll explain step by step how to find, add and integrate a specific set of React Hooks components from the popular wagmi.sh library created by @tmm on github. Wagmi gives your application the ability to connect to any browser injected wallet e.g. Metamask, BraveWallet, Rabby... , any mobile wallet that supports Wallet Connect (most) and Coinbase wallet. For a newly launched NFT minting site, DAO, or airdrop claim page this is a useful addition to your app which will allow a lot more people to interact with you application.

What this doesn’t cover:

Smart contracts. I will use a very simple React front end generated by the yarn create react-app NAME_OF_YOUR_APP command and a very simple NFT contract based off the npx hardhat command called after you’ve cd NAME_OF_YOUR_APP into your application directory. The code I have written is here on my Github and is similar to the tutorial built by Jeff Delaney @ Fireship. At the end of this you should have a simple connect wallet popup that looks like the one above, interacts with ENS and, connects with your app as you intend.

Let’s get started!

Step 1: A barebones NFT minting site

Let’s start with the most basic possible setup you would need to create and mint an NFT collection. Below is my ‘lilNFTs’ smart contracts which will allow 10,000 NFT to be minted from it for 0.08 ETH each

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract lilNFTs is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    mapping(string => uint8) existingURIs;

    constructor() ERC721("lilNFTs", "LILN") {}

    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://";
    }

    function safeMint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
        existingURIs[uri] = 1;
    }

    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function isContentOwned(string memory uri) public view returns (bool) {
        return existingURIs[uri] == 1;
    }

    function payToMint(
        address recipient,
        string memory metadataURI
    ) public payable returns (uint256) {
        require(existingURIs[metadataURI] != 1, 'NFT already minted!');
        require (msg.value >= 0.05 ether, 'Need to pay up!');

        uint256 newItemId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        existingURIs[metadataURI] = 1;

        _mint(recipient, newItemId);
        _setTokenURI(newItemId, metadataURI);

        return newItemId;
    }

    function count() public view returns (uint256) {
        return _tokenIdCounter.current();
    }

}

Next, here’s my frontend I want users to interact with the NFT minting contract. I import my compiled ‘lilNFTs’ abi data and am then able to interact with my deployed contract, in this case running on my local hardhat network.

import { useEffect, useState } from 'react';
import { useProvider, useContract, useSigner } from 'wagmi';
import placeholder from '../img/placeholder.png';

import { ethers } from 'ethers';
import lilNFTs from '../artifacts/contracts/MyNFT.sol/lilNFTs.json';
import { doc } from 'prettier';

const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";

const provider = new ethers.providers.Web3Provider(window.ethereum);

// get the end user
const signer = provider.getSigner();

// get the smart contract
const contract = new ethers.Contract(contractAddress, lilNFTs.abi, signer);

export const Home = () => {

  const [totalMinted, setTotalMinted] = useState(0);
  useEffect(() => {
    getCount();
  }, []);

  const getCount = async () => {
    const count = await contract.count();
    console.log(parseInt(count));
    setTotalMinted(parseInt(count));
  };

  const NFTImage = ({ tokenId, getCount }) => {
    const contentId = 'Qmdbpbpy7fA99UkgusTiLhMWzyd3aETeCFrz7NpYaNi6zY';
    const metadataURI = `${contentId}/${tokenId}.json`;
    const imageURI = `https://gateway.pinata.cloud/ipfs/${contentId}/${tokenId}.png`;
    const [isMinted, setIsMinted] = useState(false);

    useEffect(() => {
      getMintedStatus();
    }, [isMinted]);

    const getMintedStatus = async () => {
      const result = await contract.isContentOwned(metadataURI);
      console.log(result)
      setIsMinted(result);
    };

    const mintToken = async () => {
      const connection = contract.connect(contract.signerOrProvider);
      const addr = connection.address;
      const result = await contract.payToMint(addr, metadataURI, {
        value: ethers.utils.parseEther('0.05'),
      });
      await result.wait();
      getMintedStatus();
      getCount();
    };

    async function getURI() {
      const uri = await contract.tokenURI(tokenId);
      alert(uri);
    }

    return (
      <div>
        <img src={isMinted ? imageURI : 'img/placeholder.png'}></img>
        <div >
          <h5 >ID #{tokenId}</h5>
          {!isMinted ? (
            <button onClick={mintToken}>
              Mint
            </button>
          ) : (
            <button onClick={getURI}>
              Taken! Show URI
            </button>
          )}
        </div>
      </div>
    );
  }

  return (
    <div>
      <h1>lilNFTs Collection</h1>
      <div>
        <div>
          {Array(totalMinted + 1)
            .fill(0)
            .map((_, i) => (
              <div key={i}>
                <NFTImage tokenId={i} getCount={getCount} />
              </div>
            ))}
        </div>
      </div>
    </div>
  );
}

export default Home;

The above Home.jsx directory I import into my base App.jsx directory and get a localhost page display looking like this upon running ‘yarn dev’ or ‘npm start’ in your terminal.

Screen Shot 2022-04-10 at 1.11.09 PM

Currently in my App.jsx directory the only thing checking if I am able to connect to a wallet is a simple if statement.

if (window.ethereum) { return <Home />; } else { return <h1>Please install MetaMask</h1>; }

This is the bare minimum needed to connect a users browser injected wallet to a minting contract. In the next section I’ll explain how you can change this adding a package and integrate more wallet specific hooks.

Step 2: Adding wagmi.sh to your project

Now, we have a simple NFT minting contract and minting page. In order to get the ‘connect wallet’ component added to my app I first need to add the wagmi.sh library. I add it with yarn add wagmi ethers or with npm npm install wagmi ethers. The ‘ethers’ on the end of the command adds the ethers.js library which wagmi is built on. No other dependencies are needed!

I can now start to integrate the different hooks into my app. In App.jsx I need to first import the <Provider> component and wrap my entire app in it the like this.

import { WagmiProvider } from 'wagmi';

const App = () => (  
  <WagmiProvider>    
    <Example />
    <Home />  
  </WagmiProvider>
)

This allows any of the future components I import into my application to interact with the same wallet connection easily. Next, in order to connect to the three main wallet types I need to import them from ‘wagmi’ and add a function which allows a user to call the windows.ethereum object for the wallet of their choice. I’ll add this with a connectors function like this right above my App function.

// Set up connectors
const connectors = ({ chainId }) => {
  const rpcUrl =
    chains.find((x) => x.id === chainId)?.rpcUrls?.[0] ??
    chain.mainnet.rpcUrls[0]
  return [
    new InjectedConnector({
      chains,
      options: { shimDisconnect: true },
    }),
    new WalletConnectConnector({
      options: {
        infuraId,
        qrcode: true,
      },
    }),
    new WalletLinkConnector({
      options: {
        appName: 'My wagmi app',
        jsonRpcUrl: `${rpcUrl}/${infuraId}`,
      },
    }),
  ]
}

All that needs to be done now is set the connectors function to be called on any window.ethereum object via the <WagmiProvider> component below like this <WagmiProvider autoConnect connectors={connectors}>

Now my App.jsx will look something like this

import { WagmiProvider, chain, defaultChains } from 'wagmi'
import { InjectedConnector } from 'wagmi/connectors/injected'
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect'
import { WalletLinkConnector } from 'wagmi/connectors/walletLink'

import { Example } from './components/Example'
import { Home } from './components/noCssHome'

// API key for Ethereum node
// Two popular services are Infura (infura.io) and Alchemy (alchemy.com)
const infuraId = process.env.INFURA_ID

// Chains for connectors to support
const chains = defaultChains

// Set up connectors
const connectors = ({ chainId }) => {
  const rpcUrl =
    chains.find((x) => x.id === chainId)?.rpcUrls?.[0] ??
    chain.mainnet.rpcUrls[0]
  return [
    new InjectedConnector({
      chains,
      options: { shimDisconnect: true },
    }),
    new WalletConnectConnector({
      options: {
        infuraId,
        qrcode: true,
      },
    }),
    new WalletLinkConnector({
      options: {
        appName: 'My wagmi app',
        jsonRpcUrl: `${rpcUrl}/${infuraId}`,
      },
    }),
  ]
}

const App = () => (
  <WagmiProvider autoConnect connectors={connectors}>
    <Example />
    <Home />
  </WagmiProvider>
)

export default App;

Step 3: Wire the wallet connection component up to our minting contract

In order to be able to mint NFTs with our newly available wallets I now need to change the minting function to interact with the wagmi library hooks. I’ll do this by going into my Home.jsx file and and importing the useContract and useSigner hooks like so import { useContract, useSigner } from 'wagmi'; which will allows me to interact with my deployed contract via the <WagmiProvider> component I added in step 2. Now to connect these hooks I add them inside my Home function replacing the signer, provider, and contract constants which I declared previously. The useSigner hook will bring the connected wallets public address into the frontend and allow the app to make signing requests to which ever wallet is connected rather than just the ethereum object in the browser window (usually your MetaMask public address). This will replace both the provider and signer constants I declared earlier. Then the useContract hook will replace our contract constant bringing in the deployed smart contracts address, .json abi file, and the useSigners data object. Lastly I replace the use of the signer constant further down in my async mintToken function also with the useSigners data object leaving my Home.jsx file looking like this.

import { useEffect, useState } from 'react';
import { useContract, useSigner } from 'wagmi';
import placeholder from '../img/placeholder.png';

import { ethers } from 'ethers';
import lilNFTs from '../artifacts/contracts/MyNFT.sol/lilNFTs.json';

const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3";

export const Home = () => {

  // get the end user
  const [{ data }] = useSigner();

  // get the smart contract
  const contract = useContract({
    addressOrName: contractAddress,
    contractInterface: lilNFTs.abi,
    signerOrProvider: data,
  })

  const [totalMinted, setTotalMinted] = useState(0);
  useEffect(() => {
    getCount();
  }, []);

  const getCount = async () => {
    const count = await contract.count();
    console.log(parseInt(count));
    setTotalMinted(parseInt(count));
  };

  const NFTImage = ({ tokenId, getCount }) => {
    const contentId = 'Qmdbpbpy7fA99UkgusTiLhMWzyd3aETeCFrz7NpYaNi6zY';
    const metadataURI = `${contentId}/${tokenId}.json`;
    const imageURI = `https://gateway.pinata.cloud/ipfs/${contentId}/${tokenId}.png`;
    const [isMinted, setIsMinted] = useState(false);

    useEffect(() => {
      getMintedStatus();
    }, [isMinted]);

    const getMintedStatus = async () => {
      const result = await contract.isContentOwned(metadataURI);
      console.log(result)
      setIsMinted(result);
    };

    const mintToken = async () => {
      const connection = contract.connect(contract.signerOrProvider);
      const addr = connection.address;
      const result = await contract.payToMint(addr, metadataURI, {
        value: ethers.utils.parseEther('0.05'),
      });
      await result.wait();
      getMintedStatus();
      getCount();
    };

    async function getURI() {
      const uri = await contract.tokenURI(tokenId);
      alert(uri);
    }

    return (
      <div>
        <img src={isMinted ? imageURI : 'img/placeholder.png'}></img>
        <div >
          <h5 >ID #{tokenId}</h5>
          {!isMinted ? (
            <button onClick={mintToken}>
              Mint
            </button>
          ) : (
            <button onClick={getURI}>
              Taken! Show URI
            </button>
          )}
        </div>
      </div>
    );
  }

  return (
    <div>
      <h1>lilNFTs Collection</h1>
      <div>
        <div>
          {Array(totalMinted + 1)
            .fill(0)
            .map((_, i) => (
              <div key={i}>
                <NFTImage tokenId={i} getCount={getCount} />
              </div>
            ))}
        </div>
      </div>
    </div>
  );
}

export default Home;

At this point my NFT minting frontend should be able to interact with any connect wallet. All that is left if creating some buttons to display our available wallet connection options and add some CSS.

Step 4: connect wallet buttons and CSS

To display the different available wallet connections I’ll create a new file called Connectors.jsx and and import useAccount and useConnect from wagmi. Here I’ll add the ability to display ENS names and avatars if the connected wallet has them set and allow a user to connect and disconnect their wallet at will rather the page automatically taking it. This is easily addable because of the wagmi library.

import { useAccount, useConnect } from 'wagmi'

export const Connectors = () => {
  const [{ data, error }, connect] = useConnect()
  const [{ data: accountData }, disconnect] = useAccount({
      fetchEns: true,
  })

  if (accountData) {
      return (
          <div>
              <div>
                <img src={accountData.ens?.avatar} alt="ENS Avatar" />
                <div>
                    {accountData.ens?.name
                        ? `${accountData.ens?.name} (${accountData.address})`
                          : accountData.address}
                </div>
                <div>Connected to {accountData.connector.name}</div>
                <button
                onClick={disconnect}>
                  Disconnect
             </button>
              </div>
          </div>
      )
    }

  return (
    <div>
      <div>
        {data.connectors.map((connector) => (
          <button
            disabled={!connector.ready}
            key={connector.id}
            onClick={() => connect(connector)}
          >
            {connector.name}
            {!connector.ready && ' (unsupported)'}
          </button>
        ))}

        {error && <div>{error?.message ?? 'Failed to connect'}</div>}
      </div>
    </div>
  )
}

I will then add the <Connectors > component inside my App function in App.jsx. Now I have the ability to connect/disconnect to any type of wallet, display a users wallet address, ENS name and avatar, and easily mint and interact with any connected smart contract.

To finish off I added some inline CSS using the style={{ }} JSX object to produce the simple page layout you see below. In order not to make this write up to long I suggest viewing it on my Github or just writing your own.

Screen Shot 2022-04-11 at 10.58.41 AM

Boom! Your users can now sign in with multiple wallets!

This tutorial covers front end wallet integration. If you'd like make your user experience even easier and accept any token from any chain, check us out at Brydge!

Happy to answer questions below or in our Discord!

Top Posts


Recent Posts


More Stories

Cover Image for Liquidity Pool Deposit Launch

Liquidity Pool Deposit Launch

Brydge Liquidity enables your users to add liquidity to your pools in 1-click with any token from any supported chain

Zach Rosen, Co-Founder
Zach Rosen, Co-Founder
Cover Image for Unifying All Balances Across All Supported Chains

Unifying All Balances Across All Supported Chains

Until today, you’ve needed to choose a source chain from before viewing your token balances. We’ve received tons of feedback that you want this to be simpler. Welcome, unified token list.

Zach Rosen, Co-Founder
Zach Rosen, Co-Founder

© 2022 Brydge Inc. All rights reserved

Product

OverviewDocsBook a DemoIntegratePlaygroundAuditReport a Bug