paint-brush
Create, Deploy and Mint Smart Contract (ERC-721) with NodeJS + Hardhat + Walletconnect + Web3modalby@igaponov
2,190 reads
2,190 reads

Create, Deploy and Mint Smart Contract (ERC-721) with NodeJS + Hardhat + Walletconnect + Web3modal

by Igor GaponovSeptember 28th, 2022
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

We will create from scratch ERC-721 smartcontract, deploy it to Ethereum Goerli network using Hardhat + Solidity. For minting we will use NextJs + WalletConnect + Web3Modal

Company Mentioned

Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Create, Deploy and Mint Smart Contract (ERC-721) with NodeJS + Hardhat + Walletconnect + Web3modal
Igor Gaponov HackerNoon profile picture

We will create from scratch ERC-721 smartcontract, deploy it to Ethereum Goerli network using Hardhat + Solidity. For minting we will use NextJs + WalletConnect + Web3Modal

Before we start, I would like to mention, that you can use any network you want. With the help of this article, you can deploy to Mainnet also.


Smartcontract repo (erc721): https://github.com/gapon2401/erc721

Mint page repo (erc721-mint): https://github.com/gapon2401/erc721-mint

Smartcontract: Etherscan Goerli link

We need to go through the following steps to reach our goal:

  1. Creating NFT (ERC-721) smartcontract
  2. Configure hardhat.config.ts and .env
  3. Create deploy function
  4. Create tests
  5. Gas price for deployment and minting the smartcontract
  6. Creating images and metadata for NFT
  7. Deploy and verify smartcontract
  8. Create demo website for minting
  9. (optional) Deep dive and customize


Step 1. Creating NFT (ERC-721) smartcontract

Create new project and install https://hardhat.org — Ethereum development environment. Let’s call this project erc721.

In command line run

npx hardhat

Select Create a Typescript project .

In order to continue you will be asked to install some dependencies. The text may vary on the hardhat version.

Hardhat installation


I will run the following, because I will use yarn :

yarn add -D hardhat@^2.10.2 @nomicfoundation/hardhat-toolbox@^1.0.1

We will use https://www.openzeppelin.com/ template for ERC-721 smartcontract, where all necessary functions have been implemented. You can take a look on it: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol

Install openzeppelin packages and dotenv:

yarn add -D @openzeppelin/contracts dotenv

Remove everything from the folder contracts and create a file ERC721Contract.sol with the contents:

https://gist.github.com/gapon2401/d6bf2f0eef9b7e6721b98028f890004e

If you have any difficulties with the code above, feel free to read more about the Solidity. Here is the summary:

  • constructor — on deployment we will set the base URI for all tokens, name and a symbol to the token collection.
  • mintByOwner — is our function, which will create new NFT’s for us. Only owner of the contract can call it.
  • mint — public mint.
  • setBaseURI — owner of the contract can change the base URI for tokens
  • withdraw —owner can call this function to transfer money from the contract to it’s own wallet address

All other necessary functions are inhereted in this contract from openzeppelin.

The price of the tokens will be 0.1 ETH:

uint256 public constant PRICE = 100000000000000000;


Step 2. Configure hardhat.config.ts and .env

Install the following dependencies in order to escape the errors in developing 😲

yarn add -D ethers @nomiclabs/hardhat-etherscan @typechain/hardhat hardhat-gas-reporter solidity-coverage @types/chai @types/mocha typescript ts-node @nomicfoundation/hardhat-chai-matchers chai @nomicfoundation/hardhat-network-helpers typechain @nomiclabs/hardhat-waffle @typechain/ethers-v5 @nomiclabs/hardhat-ethers

Now configure your hardhat.config.ts :

https://gist.github.com/gapon2401/4bd368815c075954f7cac6b43e1cd687

What do we have here:

  • solidity — we specified the settings and enabled optimization
  • networks — networks, that we are going to use
  • gasReporter — will show us gas units, that we need to spend for deployment or calling the functions
  • etherscan — plugin for integration with Etherscan’s contract verification service

The next step is to create .env file in project root. You have to create 5 variables:

For instance, infura URL for MAINNET_URL:

Infura mainnet url


Infura URL for GOERLI_URL:

Infura goerli url


Now compile the code with the command:

npx hardhat compile

After that command some new folders will appear: artifacts , cache , typechain-types .


Step 3. Create deploy function

It’s time to write deploy function, we will use tasks for it.

Remove folder scripts , we don’t need it.

Create the folder tasks and the file deploy.ts inside it:

https://gist.github.com/gapon2401/6dbb24a1308479d934897abf743c32e1

In this task we get smartcontract and deploy it. After the deployment in console we will see the address of the contract.

We will come back to that function later.


Step 4. Create tests

We will write the tests for every exteral/public function.

Clear the folder test and create index.ts inside it with the contents:

https://gist.github.com/gapon2401/47162e377dde919f06108905ecdb847f

Run npx hardhat test .

You should get similar to that:

npx harhdat test


All tests passed successfully!


Step 5. Gas price for deployment and minting the smartcontract

For determine the price of deployment and minting functions we have used module hardhat-gas-reporter . It was enabled by default in hardhat.config.ts , that’s why after all tests were passed, we got the table:

Gas units table


In the table we can see the gas consumption for methods and deployment.

Deployment gas units


This is the units of gas we need to deploy the contract.

How much it will cost in ETH?

Use the formula:

(gas units) * (gas price per unit) = gas fee in gwei

In this formula we don’t know the amount of gas price per unit .

You can use https://ethgasstation.info/ or any other website to find this information. At the moment of writing this article, gas price is 17.

Gas price per unit


The value can change greatly depending on the time of day. It may be 2 or 10 or 50 🙃. Be careful.

So, the average cost will be:

Deployment = 1 697 092 * 17 = 28 850 564 gwei = 0,028850564 ETH
mint = 88 753 * 17 = 1 508 801 gwei = 0,001508801 ETH
mintByOwner = 91 060 * 17 = 1 548 020 gwei = 0,001548020 ETH
setBaseURI = 32 153 * 17 = 546 601 gwei = 0,000546601 ETH
withdraw = 30 421 * 17 = 517 157 gwei = 0,000517157 ETH

Now the ETH/USD price is $1 592, it means that deployment to mainnet will cost about $46 and calling a mintfunction will cost about $2.4.


Step 6. Creating images and metadata for NFT

Images and metadata for NFTs can be stored on your server or in IPFS.

We will cover the first case. Assume, that you have a domain

https://my-domain.com

All NFT images will store here:

https://my-domain.com/collection/assets/

All metadata here:

https://my-domain.com/collection/metadata/

So, the base URI for our smartcontract will be

https://my-domain.com/collection/metadata/

In our smartcontract by default all token URI’s will be counted from 0. Therefore, files for metadata should be like this:

0.json
1.json
....
1234.json

If you use mintByOwner function, then you can change the name of the file for specific NFT, and it can be like this: custom.json

Upload NFT images on your server. They should be available by links:

https://my-domain.com/collection/assets/0.png
https://my-domain.com/collection/assets/1.png
https://my-domain.com/collection/assets/2.png

Now create metadata for NFT’s.

Here is an example of one file 0.json :

{
    "name": "Test NFT #0",
    "image": "https://my-domain.com/collection/assets/0.png"
}

Pay attention, that in image we have defined the link to the image, which has been upoaded to the server.

Read more about opensea metadata and available values in it.

Upload your metadata files, so they should be available by links:

https://my-domain.com/collection/metadata/0.json
https://my-domain.com/collection/metadata/1.json
https://my-domain.com/collection/metadata/2.json


Step 7. Deploy and verify smartcontract

Everything is ready for deployment.

Take a look on our tasks/deploy.ts, we’ve specified baseUri as

https://my-domain.com/collection/metadata/

This is because all our metadata is available by this address.

Here you can change the name and the symbol of the collection.

In command line run:

npx hardhat deploy --network goerli

This command will deploy smartcontract to Goerli network. If you want to deploy to mainnet, you can run:

npx hardhat deploy --network mainnet

All these networks were described on hardhat.config.ts file.

After the deployment you will get the following in your console:

Deployment of smartcontract


My smartcontract address is:

0xc191B6505B16EBe5D776fb30EFbfe41A9252023a

From now, it is available on etherscan testnet:

https://goerli.etherscan.io/address/0xc191B6505B16EBe5D776fb30EFbfe41A9252023a

We need to verify our smartcontract, so everyone can check the code.

Wait about 5 minutes and try to verify it. If you get an error while verifying, don’t worry, just try to wait more time. In Mainnet I was waiting for 10–15 minutes.

In general verify function looks like this:

npx hardhat verify --network <YOUR_NETWORK> <DEPLOYED_CONTRACT_ADDRESS> <arg1> <arg2> <argn> 

Let’s try to determine, what to specify in our verify function:

  • <YOUR_NETWORK> - goerli
  • <DEPLOYED_CONTRACT_ADDRESS> will be my contract address
0xc191B6505B16EBe5D776fb30EFbfe41A9252023a
  • <arg1> — this is our base URI. You will find it in tasks/deploy.ts
"https://my-domain.com/collection/metadata/"
  • <arg2> — name of the token collection, that was specified in deployment:
"My ERC721 name"
  • <arg3> — symbol of the token collection, that was specified in deployment:
"MyERC721Symbol"

Arguments should be the same, that were in tasks/deploy.ts !!!

So, in command line I will run:

npx hardhat verify --network goerli 0xc191B6505B16EBe5D776fb30EFbfe41A9252023a "https://my-domain.com/collection/metadata/" "My ERC721 name" "MyERC721Symbol"

The result should be the next:

Verified smartcontract


On this page you can check the code of deployed smartcontract.


🥳We have finished with the smartcontract. Here is the repo, where you can find all the code.


Step 8. Create demo website for minting

This will be our second project erc721-mint .

For demo website I will use NextJS.

To speed up the development clone my repository: https://github.com/gapon2401/erc721-mint

Install all dependencies:

yarn install

Open file .env and fill in the variables:

  • NEXT_PUBLIC_SMARTCONTRACT_ADDRESS — address of your smartcontract from the paragraph Deploy and verify smartcontract.
  • NEXT_PUBLIC_SMARTCONTRACT_NETWORK — the network, where your smartcontract was deployed. We need this to switch the network. Specify goerli, because we have deployed our smartcontact there.
  • NEXT_PUBLIC_SMARTCONTRACT_INFURA_URL — network endpoint. You can use https://infura.io to get the url.

Go to your dashboard, select your project and copy Api Key :

Infura API key


In our first smartcontract project erc721open the file:

artifacts/contracts/ERC721Contract.sol/ERC721Contract.json

Copy abi :

artifacts/contracts/ERC721Contract.sol/ERC721Contract.json — abi


In current project erc721-mint open the file:

src/components/smartcontract.ts

Replace the abivalue.

Everything is done! 🎉 You can run the project yarn dev and try to use it!

In the next section I will cover the main components.


Step 9 (optional). Deep dive and customize

Take a look on main components:

  1. src/components/smartcontract.ts

Contains information about the smartcontract. It's better to dynamically load this file from the server, because we want our web page loads as soon as possible. In this demo mint form appears after the page loads. 2. src/components/Web3Provider.tsx  This component communicates with blockchain. There are some functions and variables stored in React Context:

  • connected - has user connected the wallet or not
  • isConnecting - the state of reconnecting
  • connect() - function for connecting to network
  • reconnect() – function for reconnecting. Cached provider will be used, that was selected by user at the last time
  • disconnect()– function for disconnecting network – selected by user
  • network: goerli, mainnet etc
  • provider – provider for communicating with the wallet: metamask, ledger, tokenary, etc
  • signer- the owner of the wallet
  • chainId – ID of the selected network
  • accountId – your wallet address
  • error - error message for web3Modal
  1. src/components/MintForm.tsx This component displays form for minting. Function mint() is called after the user clicks on the mint button. Now look at the code:

    const transactionResponse = await Contract.mint({ 
      value: ethers.utils.parseEther('0.1'), 
    })
    

    We are calling mint function from the smartcontract and send 0.1 ETH to it, because this function requires exactly that value.

    What if we want to call mintByOwner?

    We can do it like this:

    const transactionResponse = await Contract.mintByOwner( '0x01234566', 'token_uri' ) 
    

    But pay attention, that this function can be called only by the owner of the contract. Why? We have used onlyOwner modifier in smartcontract:

    function mintByOwner(address to, string calldata tokenUri) external onlyOwner 
    

    A few more words about that function.

    0x01234566 - this is the address, for which you want to mint token. Make sure to write full address.

    token_uri - this is custom token URI. In our deployed smartcontract the full address to token metadata looks like:

    ${baseUri + token_uri}.json 
    

    Remember, we have specified baseUri:

    https://my-domain.com/collection/metadata/
    

    So, the full address to metadata with token URI equals token_url will be:

    https://my-domain.com/collection/metadata/token_url.json
    

    Also, we don't need to pay for this transaction, except gas price.

    Function prepareNetwork() .

    On first connection this function can propose to change the network. In this function are used only goerli and mainnet networks. You can change it, if you need, here:

    chainId: smartContract.network === 'goerli' ? '0x5' : '0x1', 
    

    Function getBalanceOf .

    This function checks is it enough money on the wallet to make a transaction. In the file you may find commented code in two places:

    /* reconnect ,*/
    

    and

    /* Reconnect to the wallet */ 
    // useEffect(() => {
     //   ;(async () => {
     //     await reconnect()
     //   })()
     // }, [reconnect]) 
    

    Uncomment it to make reconnections every time the page reloads.


    Thank you for reading! ❤