Create, deploy and mint smart contract (ERC-721) with NodeJS + Hardhat + Walletconnect + Web3modal
Tags: web3, nft, erc721, hardhat
Published: 2022-09-30 07:52:03

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.

---

Demo: [https://gapon2401.github.io/erc721-mint/](https://gapon2401.github.io/erc721-mint/)
Smartcontract repo (erc721): [https://github.com/gapon2401/erc721](https://github.com/gapon2401/erc721)
Mint page repo (erc721-mint): [https://github.com/gapon2401/erc721-mint](https://github.com/gapon2401/erc721-mint)
Smartcontract: [Etherscan Goerli link](https://goerli.etherscan.io/address/0xc191B6505B16EBe5D776fb30EFbfe41A9252023a)

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

1. [Creating NFT (ERC-721) smartcontract](#step-1-creating-nft-erc721-smartcontract)
2. [Configure hardhat.config.ts and .env](#step-2-configure-hardhatconfigts-and-env)
3. [Create deploy function](#step-3-create-deploy-function)
4. [Create tests](#step-4-create-tests)
5. [Gas price for deployment and minting the smartcontract](#step-5-gas-price-for-deployment-and-minting-the-smartcontract)
6. [Creating images and metadata for NFT](#step-6-creating-images-and-metadata-for-nft)
7. [Deploy and verify smartcontract](#step-7-deploy-and-verify-smartcontract)
8. [Create demo website for minting](#step-8-create-demo-website-for-minting)
9. [(optional) Deep dive and customize](#step-9-optional-deep-dive-and-customize)

--- 

### Step 1. Creating NFT (ERC-721) smartcontract

Create new project and install [https://hardhat.org](https://hardhat.org) - Ethereum development environment. Let's call this project `erc721`.

In command line run

```bash
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5smmlwcyhqyh0vzzrixq.jpg)

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

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

We will use [https://www.openzeppelin.com/](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](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol)

Install openzeppelin packages and dotenv:

```bash
yarn add -D @openzeppelin/contracts dotenv
```

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

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

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

contract ERC721Contract is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    // Mint price - 0.1 ETH
    uint256 public constant PRICE = 100000000000000000;

    string private baseURI;

    constructor(string memory _baseTokenURI, string memory _name, string memory _symbol)
    ERC721(_name, _symbol) {
        baseURI = _baseTokenURI;
    }

    /**
     * @notice Contract might receive/hold ETH as part of the maintenance process.
     */
    receive() external payable {}

    /**
     * @notice Mint only for owner
     */
    function mintByOwner(address to, string calldata tokenUri) external onlyOwner
    {
        uint256 newItemId = _tokenIds.current();
        _tokenIds.increment();

        _safeMint(to, newItemId);
        _setTokenURI(newItemId, tokenUri);
    }


    /**
     * @notice Public mint
     */
    function mint() external payable
    {
        require(PRICE <= msg.value, "INVALID_PRICE");

        uint256 newItemId = _tokenIds.current();
        _tokenIds.increment();
        _safeMint(msg.sender, newItemId);

        // Return money, if sender have sent more than 0.1 ETH
        if (msg.value > PRICE) {
            Address.sendValue(payable(msg.sender), msg.value - PRICE);
        }
    }

    /**
     * @notice Read the base token URI
     */
    function _baseURI() internal view override returns (string memory) {
        return baseURI;
    }

    /**
     * @notice Update the base token URI
     */
    function setBaseURI(string calldata uri) external onlyOwner {
        baseURI = uri;
    }

    /**
     * @notice Allow withdrawing funds
     */
    function withdraw() external onlyOwner {
        uint256 balance = address(this).balance;
        Address.sendValue(payable(msg.sender), balance);
    }

    // The following functions are overrides required by Solidity.
    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(tokenId);
    }

    /**
     * @notice Add json extension to all token URI's
     */
    function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory)
    {
        return string(abi.encodePacked(super.tokenURI(tokenId), '.json'));
    }
}
```

If you have any difficulties with the code above, feel free to read more about the [Solidity](https://docs.soliditylang.org/). 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:

```solidity
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 😲

```bash
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`:

```javascript
import * as dotenv from "dotenv";

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

import "./tasks/deploy";

dotenv.config();

const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.14",
    settings: {
      metadata: {
        bytecodeHash: "none",
      },
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  networks: {
    goerli: {
      url: process.env.GOERLI_URL,
      accounts:
        process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
    },
    mainnet: {
      url: process.env.MAINNET_URL,
      accounts:
        process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
    },
  },
  gasReporter: {
    enabled: !!process.env.REPORT_GAS,
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY,
  },
};

export default config;
```

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:

- `PRIVATE_KEY` - the private key of the wallet, that wil be the owner of the contract

- `ETHERSCAN_API_KEY` - etherscan key. Register on [https://etherscan.io](https://etherscan.io) and on the page [https://etherscan.io/myapikey](https://etherscan.io/myapikey) create and copy your key:

![Etherscan API key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kb8h9rbafo35e98yhkwi.jpg)

- `REPORT_GAS=1` - Are we going to use gas reporter or not. We will use it

- `GOERLI_URL` and `MAINNET_URL` - use [https://infura.io/](https://infura.io/) or  [https://www.alchemy.com/](https://www.alchemy.com/) for getting the urls.

For instance, infura URL for `MAINNET_URL`:

![Infura mainnet url](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mbjs0542u9czd2ib0et.jpg)

Infura URL for `GOERLI_URL`:

![Infura goerli url](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5sasb9mmwbvwt02qxa8.jpg)

Now compile the code with the command:

```bash
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:

```javascript
import { task } from "hardhat/config";

task("deploy").setAction(async function (_, { ethers, run }) {
  console.log("Start deploying");
  try {
    // The path to token metadata
    const baseUri = "https://my-domain.com/collection/metadata/";
    const _name = "My ERC721 name";
    const _symbol = "MyERC721Symbol";
    const ERC721Contract = await ethers.getContractFactory("ERC721Contract");
    const erc721Contract = await ERC721Contract.deploy(baseUri, _name, _symbol);

    await erc721Contract.deployed();

    console.log("Contract deployed to address:", erc721Contract.address);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
});
```

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:

```javascript
import { ethers } from "hardhat";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";
import { ERC721Contract } from "../typechain-types";
import { expect } from "chai";

interface Signers {
  admin: SignerWithAddress;
  user: SignerWithAddress;
}

describe("ERC721Contract", function () {
  before(async function () {
    this.signers = {} as Signers;
    const signers: SignerWithAddress[] = await ethers.getSigners();
    this.signers.admin = signers[0];
    this.signers.user = signers[1];

    this.baseUri = "https://my-domain.com/collection/metadata/";
  });

  beforeEach(async function () {
    const _name = "My ERC721 name";
    const _symbol = "MyERC721Symbol";

    const ERC721ContractFactory = await ethers.getContractFactory(
      "ERC721Contract"
    );

    this.contract = <ERC721Contract>(
      await ERC721ContractFactory.deploy(this.baseUri, _name, _symbol)
    );

    await this.contract.deployed();
  });

  it("Mint 5 tokens by owner", async function () {
    await this.contract.mintByOwner(
      this.signers.user.address,
      "link_to_token_json1"
    );
    await this.contract.mintByOwner(
      this.signers.user.address,
      "link_to_token_json2"
    );
    await this.contract.mintByOwner(
      this.signers.user.address,
      "link_to_token_json3"
    );
    await this.contract.mintByOwner(
      this.signers.user.address,
      "link_to_token_json4"
    );
    await this.contract.mintByOwner(
      this.signers.user.address,
      "link_to_token_json5"
    );

    // Check balance of the user
    expect(await this.contract.balanceOf(this.signers.user.address)).to.equal(
      5
    );
  });

  it("Revert, if user is trying to use mintByOwner", async function () {
    expect(
      this.contract
        .connect(this.signers.user)
        .mintByOwner(this.signers.user.address, "link_to_token_json1")
    ).to.be.reverted;
  });

  it("Mint token by user", async function () {
    await this.contract
      .connect(this.signers.user)
      .mint({ value: ethers.utils.parseEther("0.1") });

    expect(await this.contract.balanceOf(this.signers.user.address)).to.equal(
      1
    );
  });

  it("Should revert INVALID_PRICE", async function () {
    expect(
      this.contract
        .connect(this.signers.user)
        .mint({ value: ethers.utils.parseEther("0.05") })
    ).to.be.revertedWith("INVALID_PRICE");
  });

  it("Should return back overpaid eth to sender", async function () {
    const price = ethers.utils.parseEther("0.1");
    expect(
      this.contract
        .connect(this.signers.user)
        .mint({ value: ethers.utils.parseEther("1.5") })
    ).to.changeEtherBalances(
      [this.signers.user, this.contract],
      [price.mul(-1), price]
    );
  });

  it("Check receiving eth to contract and withdraw", async function () {
    const value = ethers.utils.parseEther("1.5");
    await this.signers.user.sendTransaction({
      to: this.contract.address,
      value,
    });

    expect(this.contract.withdraw()).to.changeEtherBalances(
      [this.signers.admin, this.contract],
      [value, value.mul(-1)]
    );
  });

  it("Set base URI", async function () {
    const value = ethers.utils.parseEther("0.1");

    // Mint 2 tokens
    await this.contract.connect(this.signers.user).mint({ value });
    await this.contract.connect(this.signers.user).mint({ value });

    // Check token URI's
    expect(await this.contract.tokenURI(0)).to.be.equal(
      this.baseUri + "0.json"
    );
    expect(await this.contract.tokenURI(1)).to.be.equal(
      this.baseUri + "1.json"
    );

    // Change base URI
    const newBaseUri = "https://new_domain_or_ipfs/";
    await this.contract.setBaseURI(newBaseUri);

    // Check new token URI's
    expect(await this.contract.tokenURI(0)).to.be.equal(newBaseUri + "0.json");
    expect(await this.contract.tokenURI(1)).to.be.equal(newBaseUri + "1.json");
  });

  it("Check token URI", async function () {
    const value = ethers.utils.parseEther("0.1");

    // Mint token by user
    await this.contract.connect(this.signers.user).mint({ value });
    // Check token URI
    expect(await this.contract.tokenURI(0)).to.be.equal(
      this.baseUri + "0.json"
    );

    // Mint token by owner with specified URI
    await this.contract.mintByOwner(this.signers.user.address, "link_to_uri");

    // Check token URI
    expect(await this.contract.tokenURI(1)).to.be.equal(
      this.baseUri + "link_to_uri.json"
    );
  });
});
```

Run `npx hardhat test`.

You should get similar to that:

![npx harhdat test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rvbwwcrkkyizc39fkl8n.jpg)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pys8e1ocr37p0q5ywttv.jpg)

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

![Deployment gas units](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1nazh1bzmzdu3w3ufgyn.jpg)

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/](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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4udq8hte45xj3f7ijqi.jpg)

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 `mint` function 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](https://en.wikipedia.org/wiki/InterPlanetary_File_System).

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 will 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 uploaded to the server.

[Read more](https://docs.opensea.io/docs/metadata-standards#metadata-structure) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ww4jfp9u4ie1pwumezx5.jpg)

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:

- &lt;YOUR_NETWORK&gt; - `goerli`

- &lt;DEPLOYED_CONTRACT_ADDRESS&gt; will be my contract address

```
0xc191B6505B16EBe5D776fb30EFbfe41A9252023a
```

- &lt;arg1&gt; - this is our base URI. You will find it in `tasks/deploy.ts`

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

- &lt;arg2&gt; - name of the token collection, that was specified in deployment:

```
"My ERC721 name"
```

- &lt;arg3&gt; - 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mjyfflrnobyly7h55xw0.jpg)

On [this page](https://goerli.etherscan.io/address/0xc191B6505B16EBe5D776fb30EFbfe41A9252023a#code) you can check the code of deployed smartcontract.

🥳We have finished with the smartcontract. Here is the [repo](https://github.com/gapon2401/erc721), 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](https://nextjs.org/).

To speed up the development clone my repository: [https://github.com/gapon2401/erc721-mint](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](#step-7-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](https://infura.io) to get the url.

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

![Infura API key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wfv6zjx5axjawzedcv6j.jpg)

In our first smartcontract project `erc721` open the file:

```
artifacts/contracts/ERC721Contract.sol/ERC721Contract.json
```

Copy `abi`:

![artifacts/contracts/ERC721Contract.sol/ERC721Contract.json- abi](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tdtt7sqjv84lemf565of.jpg)

In current project `erc721-mint` open the file:

```
src/components/smartcontract.ts
```

Replace the `abi` value.

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

3) `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:

```javascript
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:

```javascript
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:

```javascript
${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:

```javascript
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:

```javascript
/* reconnect ,*/
```

and

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

Uncomment it to make reconnections every time the page reloads.

Thank you for reading! ❤