khank8476 / versatile-wallet-connection

a step by step guide on adding a Connect Wallet component to your project allowing you to integrate your frontend with any popular wallet

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

integrating a versatile wallet connection

a step by step guide on adding a Connect Wallet component to your project allowing you to integrate your frontend with any popular wallet

Screen Shot 2022-04-01 at 9.58.08 AM.png

who is this for:

This write up is for anyone getting into blockchain development and looking to better understand and being able to connect any popular wallet to smart contracts on Ethereum using the popular plain ReactJS framework. This is intended for someone who is familiar with Javascript, ReactJS and, understands the basic workings of a command line(CLI) and the package manager Yarn. I will assume a lot of things but hopefully nothing too major and end up wasting your valuable learning time.

what i will explain:

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. This may seem oddly specific but wagmi.sh is a good library of React Hooks and integrating a wallet connection component built on the Ether.js library is a something valuable in a wide range of development scenarios. It gives your application the ability to connect to any browser injected wallet e.g. Metamask, BraveWallet, Rabby... , any mobile wallet that supports Wallet Connect (which is most of them) and CoinBase wallet which uses the WalletLink library. For a newly launched NFT minting site, DAO page, or airdrop claim page this is a useful addition to your app which will allow a lot more people to interact with you application on launch.

what i will not explain:

In this write up I will not explain how to create a smart contract of any kind or how to build a front end for your application. For the sake of example 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 closely based off the tutorial by Jeff Delaney at Fireship.io. The point of this is to explain how to integrate these three main wallets connection types into your project rather than just using window.ethereum for all you wallet interactions. 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.

the guide

step 1: bare minimum NFT minting site

At this point in your project you hopefully have an existing smart contract/set of smart contracts you either forked from somewhere or built yourself (congrats), and a frontend of some kind built with React. For mine what i have is 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 is 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.

Screen Shot 2022-04-10 at 1.11.09 PM.png

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 a specific more wallet specific hooks.

step 2: adding the wagmi.sh package to your project

Now I 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, otherwise you need no other dependencies.

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 form ‘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: integrating the wallet connection component with our minting contract

In order to be able to mint NFTs with any of the newly available wallets I now need to change the minting function to interact with the wagmi library hooks rather than directly with the ethereum object I was using before. 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.png

step 5: where to find more

If you found this helpful and are interested in other React Hooks components I would definitely recommend looking at the wagmi documentation and also that of ethers. Both are really good and pretty much where I learned all this from along with good old YT. The NFT contract and minting page I used as an example is from Fireship.io like I mention before and is available here. If you are still learning how to write and interact with smart contracts I highly recommend it too.

cheers

About

a step by step guide on adding a Connect Wallet component to your project allowing you to integrate your frontend with any popular wallet


Languages

Language:JavaScript 69.2%Language:Solidity 12.8%Language:HTML 10.8%Language:CSS 5.9%Language:Shell 1.3%