Introduction
This tutorial will show you how to:
- Let users connect their Metamask wallets to the website
- Allows users to call a contract function, make a payment, and mint an NFT.
By the end of this tutorial, you will have a fully functional web3 frontend built with React. You'll also get the basics needed to build any generic web3 frontend (except NFT minter).
premise
This tutorial assumes that you have developed and deployed a smart contract to the Rinkeby test network. If you haven't already, we highly recommend you go through this tutorial . In order to continue with this tutorial, you will need the following.
- ABI file for the smart contract (found in your project's artifacts folder).
- The address of the smart contract.
We also assume you have some experience with React and Javascript. If not, it is strongly recommended that you take a look at the official tutorial on the React website first .
set item
Let's create-react-app
start by creating a React project using . Open a terminal and run the following command:
npx create-react-app nft-collectible-frontend
The installation process will take 2-10 minutes. Once complete, check that everything is working by running the following command.
cd nft-collectible-frontend
npm start
If all goes well, you should see the browser open a new tab at localhost://3000 with the following screen. Pretty standard React content:
Now let's do some cleanup.
To go in public/index.html
, modify the site's title and meta description (this step is optional).
Next, go into the src folder and delete the App.test.js
, logo.svg
and setupTests.js
files. In this tutorial, we will not need these files.
Go into App.js
the file and replace its contents with the following template.
import './App.css';
function App() {
return (
<h1>Hello World</h1>
);
}
export default App;
App.css
All content deleted at the same time . However, do not delete this file. In later chapters we'll provide some basic styling, which should be good enough for this demo project.
If you go back to localhost, you should see a screen that says Hello World . We now have a basic react project ready to get started.
Get contract ABI and address
In order for our React frontend to connect and communicate with a smart contract, it needs the contract's ABI and address.
ABI (Application Binary Interface) is a JSON file that is automatically generated during contract compilation. We deploy to the blockchain to store smart contracts in the form of bytecode. In order to call a function on it, pass the correct parameters, and parse the return value using a high-level language, we need to specify details about the function and contract (such as name, parameters, type, etc.) to the front end. This is exactly what the ABI file does. In order to learn more about ABI, I suggest you read: How to understand Ethereum ABI .
To find your ABI file, go into your hardhat project and navigate to artifacts/contracts/NFTCollectible.sol/NFTCollectible.json
.
We now need to copy the JSON file to the React project. Inside src
the folder create a contracts
new folder called and paste NFTCollectible.json
the files.
You should already have the address of the deployed smart contract. (If you don't, just deploy it to Rinkeby again and get the latest addresses and ABI files).
Our contract address in the previous tutorial was 0x355638a4eCcb777794257f22f50c289d4189F245. We will also use this contract in this tutorial.
Now let's import the contract ABI and App.js
define the contract address in the file.
Set template HTML, CSS and JS
The website will be very simple. It will just have a title and a connect wallet button. Once the wallet is connected, the connect wallet button will be replaced by a Mint NFT button.
We're not going to go to the trouble of creating separate component files. Instead, we'll App.js
write all the HTML and logic in and App.css
all the CSS in .
Copy the contents of the following Github gist into App.js
the file.
import { useEffect } from 'react';
import './App.css';
import contract from './contracts/NFTCollectible.json';
const contractAddress = "0x355638a4eCcb777794257f22f50c289d4189F245";
const abi = contract.abi;
function App() {
const checkWalletIsConnected = () => { }
const connectWalletHandler = () => { }
const mintNftHandler = () => { }
const connectWalletButton = () => {
return (
<button onClick={connectWalletHandler} className='cta-button connect-wallet-button'>
Connect Wallet
</button>
)
}
const mintNftButton = () => {
return (
<button onClick={mintNftHandler} className='cta-button mint-nft-button'>
Mint NFT
</button>
)
}
useEffect(() => {
checkWalletIsConnected();
}, [])
return (
<div className='main-app'>
<h1>Scrappy Squirrels Tutorial</h1>
<div>
{connectWalletButton()}
</div>
</div>
)
}
export default App;
(Remember to set the correct contract address on line 5)
Note that several functions are already defined here, which currently have no effect. We'll explain their purpose below and put logic into it.
Below is the corresponding CSS, copy the following into your App.css
file.
.main-app {
text-align: center;
margin: 100px;
}
.cta-button {
padding: 15px;
border: none;
border-radius: 12px;
min-width: 250px;
color: white;
font-size: 18px;
cursor: pointer;
}
.connect-wallet-button {
background: rgb(32, 129, 226);
}
.mint-nft-button {
background: orange;
}
The site should now look like this:
Feel free to customize the look of your site by adding more styles and static elements (images, headers, footers, social media links, etc.).
We've put together most of the base building blocks for this project. We are now in a good position to tackle one of the first main goals of this tutorial: allowing users to connect their wallets to our website.
Connect Metamask wallet
In order for users to be able to call functions from our contracts, they need to be able to connect their wallets to our website. The wallet will enable users to pay the gas and sale price to mint an NFT from our collection.
In this tutorial, we will exclusively use the Metamask wallet and its set of APIs. There are ready-made solutions like Moralis and web3modal that allow you to add support for multiple wallets with very little code. But in this project, we will focus on implementing the connected wallet functionality from scratch. Solutions like Moralis will be covered in a future tutorial.
We assume you already have the Metamask wallet plugin installed in your browser. If you have, Metamask injects an ethereum
object into your browser's global window
object. We will access window.ethereum
to perform most of our functions.
Check if Metamask wallet exists
Users cannot mint NFTs on our website unless they have a Metamask wallet. Let's App
add checkWalletIsConnected
a function to the component to check if the Metamask wallet exists.
Note that we also define useEffect
hooks that check for the presence of Metamask when the App component loads.
Open the console on your application's localhost page. If you already have Metamask installed, you should see a message saying Wallet exists! We're ready to go! (*Wallet exists!* We're ready to go!).
Programmatically connect to Metamask
Just because we have the Metamask plugin installed, doesn't mean Metamask automatically connects to every website we visit. We need to prompt Metamask to ask the user to do so.
This is where the connected wallet feature comes in. It is equivalent to a login button of web3. It allows users to connect through the website and send requests to invoke contract functions.
Metamask makes it easy to connect via the "window.ethereum.request" method.
Let's first App()
define a variable in the useState hook to track the user's wallet address. (don't forget to import from React useState
)
const [currentAccount, setCurrentAccount] = useState(null);
Now, let's define connectWalletHandler
the function.
Let's briefly look at what this function does:
- Check if Metamask is installed. If not, the website will display a popup asking to install Metamask.
- It requests Metamask to provide the user's wallet address.
- Once the user agrees to connect with the website, it will fetch the first available wallet address and use it as the value of the currentAccount variable.
- If something goes wrong (such as a user refusing to connect), it will fail and print an error message to the console.
Currently, if you open the Metamask plugin on the website, it says Not conntected.
Now comes the moment, click the **Connect Wallet* button on the website. Metamask will prompt you to connect with the website. Once you agree, the plugin interface will look like this:
congratulations! The wallet has been successfully connected to the website.
Once the wallet is connected, we'd better replace the Connect Wallet button with a Mint NFT button . In the App's return value, let's replace the rendering of the Connect Wallet button with a conditional rendering .
{currentAccount ? mintNftButton() : connectWalletButton()}
The site should now look like this:
Let's refresh the page and check the plugin. You will see that Metamask shows that the website is connected (connected), but the website still shows a button to connect to the wallet .
If you're familiar with React, it should be pretty clear why this happens. After all, we only set the "currentAccount" state in the "connectWallet" function.
Ideally, the website should App
check to see if the wallet is connected every time the component loads (i.e. every refresh).
Let's plug in checkWalletIsConnected
the function to check the account as soon as the site loads, and set the currentAccount if the wallet is already connected.
(Note that this function is marked async). A brief explanation of what this function does:
- It checks if Metamask is installed and outputs the result to the console.
- It tries to request Metamask for the connected account.
- If Metamask is already connected, it will do so by giving the function a list of accounts. Returns an empty list if none.
- If the list is not empty, the function will select the first account Metamask fetches and set it as the current account.
If you refresh the page now, you'll see that the website does display the Mint NFT button.
Mint NFT from the website
Now let's implement the core functionality of the site. Here's what we want to happen when a user taps the Mint NFT button.
- Metamask prompts users to pay the price of NFT + Gas.
- Once the user accepts, Metamask calls the mintNFT function in the contract on behalf of the user.
- Once the transaction is completed, it notifies the user of the success/failure of the transaction.
To do this, we will need ethers
libraries for contract interaction. In your terminal, run the following command:
npm install ethers
Let's App.js
import this library in:
import { ethers } from 'ethers';
Finally, let's add mintNftHandler
the function:
(don't forget to mark this function as "async")
As usual, to explain what this function does:
- Attempt to access an object injected by Metamask
ethereum
. - If
ethereum
the object exists, it sets Metamask as the RPC provider. This means, the request will be made to the miner using the Metamask wallet. - In order to make a transaction request, users need to sign the transaction with their private key. So get the signer.
- A contract instance is then created with the deployed contract's address, contract ABI, and signer.
- Call the function on our contract through the above contract object. Call the mintNFT function and ask Metamask to send 0.01 ETH (this is the price we set for the NFT).
- Wait for the transaction to be processed, and once processed, output the transaction hash to the console.
- If there is any failure (bad function call, wrong parameter passed, <0.01 ETH sent, user rejected transaction, etc.), the error will be printed to the console.
On the website, open your browser's console so you can see the mining status in real time.
Now, tap the Mint NFT button. Metamask will prompt you to pay 0.01 ETH + gas. The transaction will take about 15-20 seconds to process. Once complete, the transaction can be confirmed via a Metamask popup and console output.
You can now also view the NFT on Opensea. Navigate to your account on testnets.opensea.io and you should see your latest NFT.
UX improvements and conclusions
congratulations! You now have a fully functional web3 frontend through which users can mint NFTs.
However, as you may have noticed, the user experience of the site leaves a lot to be desired. Here are some improvements you should consider making.
Make sure users are connected to the correct network
Our website assumes that users are connected to the Rinkeby network when interacting with the website, which may not always be the case.
Can you implement a way to alert the user when he is not connected to Rinkeby (like OpenSea does)? Also, make sure users can't see the Mint NFT button when they're connected to the wrong network .
show transaction status
Currently, our website prints transaction status to the console. In a real project, you can't expect your users to have their consoles open at the same time as they are interacting with the website.
Can you implement tracking transaction status and real-time feedback to users? It should display a loading prompt (loading) while the transaction is being processed, notify the user if the transaction fails, and display the transaction hash (or Opensea link) if the transaction is successful.
Prompt Metamask even if funds are low
If you don't have any ETH in your Metamask wallet, clicking on the Mint NFT will not prompt Metamask at all. In fact, the user will not receive any feedback.
Can you make sure that Metamask is prompted even if the user has insufficient funds? It would be better for Metamask to inform the user how much ETH is needed and how much he/she is short of.
other improvements
Here are some other improvements you might consider:
- Users are allowed to mint more than 1 NFT at a time.
- Add some samples of artwork from your NFT collection.
- Add a link to information about your collection on Opensea.
- Add verified smart contract addresses so people can double-check what's really happening behind the scenes.
- Add your Twitter, IG and Discord links.
Our NFT sandbox project, Rinkeby Squirrels , implements most of the UX upgrades mentioned here, and you can try minting one.
Final codebase: https://github.com/rounakbanik/nft-collectible-frontend
About Scrappy Squirrels
Scrappy Squirrels is a collection of over 10,000 randomly generated NFTs. Scrappy Squirrels is for buyers, creators, and developers who are completely new to the NFT ecosystem.
This community is built around learning about the NFT revolution, exploring its current use cases, discovering new applications, and finding members to collaborate on exciting projects together.
Join our community here: https://discord.gg/8UqJXTX7Kd