Mint Your NFT#

Before we dive into coding#

Introduction#

This tutorial aims at making you discover how to code in solidity in order to interact with the blockchain. You are going to learn how to code a Smart Contract, deploy a NFT and mint it in your wallet.

To write this assignment, I was greatly inspired by an article published on freecodecamp. This site is a real gold mine if you want to learn how to code or simply familirize yourself with the computer science world. Every content is free of access and of very good quality, have a look here : https://www.freecodecamp.org/.

πŸ›  Disclaimer This tutorial is easily accessible if you are familiar with programming tool. If it’s the first time you hear about terminal, bash, code editor or node.js, you may need to spend a little bit more time reading outside documentations to get familiar with these notions. Along this tutorial I put external links for you to get more information. It’s not wasted time for you to do so as it’s more and more useful to understand what these tools are used for in our society where computer science takes a growing part in our lives.

Open your crypto wallet#

First we need to open a wallet to store our crypto currency and our digital assets.

There are many wallet provider, but I advice you to open a MetaMak wallet as it is one of the most spread.

πŸ’‘ Here is a nice Tutorial to open a MetaMask Ethereum wallet.

Design your avatar#

We are going to code a smart contract hosting a NFT. This NFT will be your β€œavatar” for the class. Design an avatar that you will eventually mint as a NFT. It can be whatever you want but it should be a png file.

Download a source code editor#

A source code editor is a software that enables you to open (almost) any type of code file. This will make it possible for you to open, edit and write your own programs.

If you don’t already have one I advice you to download VScode or Sublime text. If you are a beginner, you should choose VScode as it provides more hand-holding and is a great option for its debugging functionality.

Here is a tutorial on how to install VScode and Sublime Text.

Install Node.JS on your computer#

We will be using NodeJS for the project.

πŸ’‘ If you don’t have it installed, follow this simple tutorial : https://phoenixnap.com/kb/install-node-js-npm-on-windows/.

Connect to an Ethereum node with Alchemy#

To interact with the Ethereum Network, you will need to be connected to an Ethereum Node.

Running your own Node and maintaining the infrastructure is a project on its own. Luckily, there are nodes-as-a-service providers which host the infrastructure for you. We will be using Alchemy as our node provider.

❓ Head over to their website, create an account, choose Ethereum as your network and create your app. You can choose Sepolia as your network. It is recommended to use your dashboard, click β€œview details” on your app, then click β€œview key”. Save your http key somewhere as we will need that later.

Also, as you noticed, we will be using the Sepolia testnet. Sepolia is a testnet i.e. an instance of Ethereum blockchain used for testing and experimentation without risk to loose real funds or the main chain. That way you’ll learn to write smart contracts and mint NFTs without wasting money if you do a mistake using the mainnet.

❓ To get some Sepolia, go to one of the faucets (e.g. Google Cloud Web3 Faucet), connect with your Alchemy account and copy paste your public key.

Deploy your Smart Contract#

Set up your repository#

We are going to use the terminal to create folders and run files throughout the tutorial. If you are not familiar with using your terminal, I suggest you read this article.

Make sure you are in the C:\Users\Name folder and run this command to create a new directory for your project:

mkdir nft-project # this will create a folder in the root file of your computer
cd nft-project # this will enter the created folder

Now, let’s make another directory, ethereum/, inside nft-project/ and initialize it with Hardhat. Hardhat is a development environment that makes it easy to deploy and test your Ethereum software.

mkdir ethereum #this will create a new folder within the nft-project one
cd ethereum #this will enter the newly created ethereum folder
npm install --save-dev hardhat # install hardhat on your computer, be careful, don't forget the "-" between save and dev otherwise it won't work
npx hardhat --init

After waiting a moment, you will see this prompt:

 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                         β–ˆβ–ˆβ–ˆ  β–ˆβ–ˆβ–ˆ                  β–ˆβ–ˆβ–ˆ      β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
β–‘β–‘β–ˆβ–ˆβ–ˆ  β–‘β–‘β–ˆβ–ˆβ–ˆ                         β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ                 β–‘β–ˆβ–ˆβ–ˆ     β–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆ
 β–‘β–ˆβ–ˆβ–ˆ   β–‘β–ˆβ–ˆβ–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  β–‘β–‘β–‘  β–‘β–ˆβ–ˆβ–ˆ
 β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  β–‘β–‘β–‘β–‘β–‘β–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆ  β–‘β–‘β–‘β–‘β–‘β–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–ˆβ–ˆβ–ˆβ–‘      β–ˆβ–ˆβ–ˆβ–ˆβ–‘
 β–‘β–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–ˆβ–ˆβ–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ β–‘β–‘β–‘ β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  β–‘β–ˆβ–ˆβ–ˆ      β–‘β–‘β–‘β–‘β–ˆβ–ˆβ–ˆ
 β–‘β–ˆβ–ˆβ–ˆ   β–‘β–ˆβ–ˆβ–ˆ  β–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ     β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ  β–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆ  β–‘β–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆ β–‘β–ˆβ–ˆβ–ˆ
 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ    β–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  β–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–‘β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
β–‘β–‘β–‘β–‘β–‘  β–‘β–‘β–‘β–‘β–‘  β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘      β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘  β–‘β–‘β–‘β–‘β–‘β–‘β–‘    β–‘β–‘β–‘β–‘β–‘   β–‘β–‘β–‘β–‘β–‘β–‘

πŸ‘· Welcome to Hardhat v3.1.12 πŸ‘·

? Which version of Hardhat would you like to use? ... 
> Hardhat 3 Beta (recommended for new projects)
  Hardhat 2 (older version)

Select Hardhat 3 Beta (recommended for new projects)

Where would you like to initialize the project?
Please provide either a relative or an absolute path: Β» .

Just press enter.

What type of project would you like to initialize? ... 
> A TypeScript Hardhat project using Node Test Runner and Viem
  A TypeScript Hardhat project using Mocha and Ethers.js
  A minimal Hardhat project

Select A TypeScript Hardhat project using Node Test Runner and Viem

√ Hardhat only supports ESM projects. Would you like to change ".\package.json" to turn your project into ESM? (Y/n) · true

Just press enter, to answer yes.

✨ Template files copied ✨
? You need to install the necessary dependencies using the following command:
npm install --save-dev "@nomicfoundation/hardhat-toolbox-viem@^5.0.3" "@nomicfoundation/hardhat-ignition@^3.0.9" "@types/node@^22.8.5" "forge-std@foundry-rs/forge-std#v1.9.4" "typescript@~5.8.0" "viem@^2.43.0"

Do you want to run it now? (Y/n) Β» true

Just press enter, to answer yes.

Define your environment#

Remember the Alchemy key we grabbed from our test project earlier? We will use that along with our Metamask account’s public and private keys to interact with the blockchain.

Run the following commands to make a file called .env inside your ethereum/ directory, and install dotenv. We will use them later.

npm install touch-cli -g #use npm to install the touch package
npm install dotenv --save #add a package
touch .env #create the .env file
code .env #open the .env fil

Put the following keys inside the .env file :

  • Alchemycode . key you have exported in the beginning

  • Private key (follow those instructions to grab your Metamask’s private key)

🚨 Be extremely careful when handling your private key. Never share it, never copy paste it in a document accessible to any other person than you.

Open the .env file with the code editor of your choice (I personnaly use VS Code, but Sublime text or any other of your choice will fit) and add your keys.

Your .env file should look like this:

SEPOLIA_RPC_URL = "YOUR_ALCHEMY_KEY"
SEPOLIA_PRIVATE_KEY = "YOUR_METAMASK_PRIVATE_KEY"

For the SEPOLIA_RPC_URL, be careful to copy paste the whole URL and not only your key.

Code the file#

Go to the ethereum/ folder and then you can see among the folders, the two following folders :

  • contracts/ which is where we’ll keep our NFT smart contract code. You can notice that it already contains β€œCounter.sol” and β€œCounter.t.sol” which are files for a test smart contract. You can look inside the files if you are curious, but it will not be useful for our project. You can either keep or delete the files, it doesn’t matter.

  • scripts/ which is where we’ll keep scripts to deploy and interact with our smart contract. It also contains test files you can keep them and check them out of curiosity or delete them it doesn’t matter for our project.

Now you can install OpenZeppelin. OpenZeppelin Contract is an open-sourced library with pre-tested reusable code to make smart contract development easier.

npm install @openzeppelin/contracts

Finally, we will be writing the Smart Contract for our NFT. Navigate to your contracts directory and create a file titled avatar.sol. You can name your NFTs however you see fit.

The .sol extension refers to the Solidity language, which is what we will use to program our Smart Contract. We will only be writing 14 lines of code with Solidity, so no worries if you haven’t seen it before.

Start with this article to learn more about Smart Contract languages. You can also directly jump to this Solidity cheat sheet which contains the main syntax.

cd contracts
touch avatar.sol //create a solidity file
code avatar.sol //opens your file with your default code editor

Open the avatar.sol file with your code editor and copy paste the following code (this is our Smart Contract):

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

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

contract Avatar is ERC721URIStorage, Ownable {

    uint256 private _tokenIdCounter;

    event Minted(address indexed to, uint256 tokenId);

    constructor() ERC721("Avatar", "AVT") Ownable(msg.sender) {}

    function mint(address to, string memory uri) public onlyOwner {

        uint256 tokenId = _tokenIdCounter;

        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);

        emit Minted(to, tokenId);

        _tokenIdCounter += 1;
    }
}

Let’s go through the code and understand what is going on:

  • At the top of the file, we import two OpenZeppelin modules: ERC721URIStorage and Ownable.

ERC721URIStorage extends the ERC721 standard and allows each NFT to store its own token URI, which points to the metadata describing the token.

Ownable provides a simple access control mechanism. It assigns an owner to the contract and allows us to restrict certain functions so that only this owner can execute them.

  • We declare a variable called _tokenIdCounter.

This variable is used to generate unique identifiers for each NFT. Each time a token is minted, the counter increases so that every NFT has a different ID.

  • In the constructor, we initialize the ERC721 contract with a name and a symbol:

constructor() ERC721("Avatar", "AVT") {}

The name represents the NFT collection, while the symbol is a shorter identifier similar to a ticker.

  • The mint function creates a new NFT.

It takes two parameters:

  • to – the address that will receive the NFT

  • uri – the metadata URI associated with the NFT

The function is restricted with onlyOwner, meaning only the contract owner can mint new tokens.

  • Inside the function, we retrieve the current token ID from _tokenIdCounter and call _safeMint, a function provided by OpenZeppelin that safely creates the NFT and assigns it to the specified address.

  • We then call _setTokenURI to associate the NFT with its metadata.

  • The contract also emits an event called Minted. Events allow external applications and blockchain explorers to track important actions happening in the contract.

  • Finally, we increment _tokenIdCounter so the next NFT minted will receive a new unique ID.

In summary, this contract allows the owner to mint NFTs and associate each token with a metadata file.

Metadata#

In an NFT project, metadata is used to store information describing the asset associated with the token.

Because storing large files directly on-chain would be extremely expensive, NFTs typically store only a reference (a URI) pointing to metadata hosted off-chain. This metadata usually contains information such as the NFT’s name, description and image.

Most modern NFT projects store metadata on IPFS (InterPlanetary File System), a decentralized storage network. Each file stored on IPFS receives a content identifier (CID) that uniquely represents the file.

For more information about NFT metadata, you can read this article .

In this tutorial, instead of creating a backend API, we will simply create a static JSON file containing our NFT metadata. This file will later be uploaded to IPFS.

Upload the image to IPFS#

To store the image associated with our NFT, we will use Pinata, a service that allows us to upload and pin files on IPFS.

  1. Go to https://pinata.cloud

  2. Create a free account and log in.

  3. In the dashboard, click +Add and then File Upload.

  4. Upload the png image that you prepared earlier.

Once the file is uploaded, Pinata will display a CID (Content Identifier) associated with your image.

You can view your file using this URL:

https://ipfs.io/ipfs/YOUR_CID

If the image appears, your file is correctly stored on IPFS.

For NFTs, however, we usually use the IPFS URI format instead of the HTTP gateway.
Your image URI should therefore look like:

ipfs://YOUR_CID

Copy this URI β€” we will use it in the metadata file.

Create the metadata file#

Now we will create a JSON file describing our NFT.

Go back to the root directory of your project and create a new folder for the metadata.

mkdir metadata
cd metadata

Create a file called avatar.json:

touch avatar.json
code avatar.json

Add the following content:

{
  "name": "Name's avatar",
  "description": "Name's avatar for the Blockchain class",
  "image": "ipfs://YOUR_IMAGE_CID"
}

Replace:

  • Name with your name

  • YOUR_IMAGE_CID with the CID of the image you uploaded to Pinata

Example:

{
  "name": "Alice's avatar",
  "description": "Alice's avatar for the Blockchain class",
  "image": "ipfs://bafkreif2le6qnrhmysftow4fvx3jujzcfbshxnt4uyt5iynwideufbsxqi"
}

This JSON file contains the metadata describing the NFT.

In the next step, we will upload this metadata file to IPFS and use its CID as the tokenURI of the NFT.

Upload the metadata to IPFS#

Now that our metadata file is ready, we need to upload it to IPFS so that it can be accessed by our NFT.

We will again use Pinata to upload the file.

  1. Go back to your Pinata dashboard

  2. Click +Add

  3. Select File Upload

  4. Upload the file avatar.json that you just created

Once the upload is complete, Pinata will display a CID for your metadata file.

You can preview the JSON file using a gateway URL such as:

https://ipfs.io/ipfs/YOUR_METADATA_CID

If everything is correct, you should see your JSON metadata displayed in the browser.

For NFTs, we again use the IPFS URI format rather than the HTTP gateway.

Your metadata URI should therefore look like this:

ipfs://YOUR_METADATA_CID

This URI will be used as the tokenURI when minting the NFT.

Your NFT will then retrieve the metadata from IPFS, which in turn references the image you uploaded earlier.

Deploy the smart contract#

Compile the smart contract#

Before deploying our contract, we first need to compile it.

From the ethereum/ directory, run:

npx hardhat compile

If everything is correct, Hardhat will compile the contract and generate the necessary artifacts.

Configure the network#

Before deploying the contract, we need to configure Hardhat so it knows which network to deploy to and which account to use.

Open the file hardhat.config.ts located in the ethereum/ directory.

Modify hardhat.config.ts so it loads the variables from .env and configures the Sepolia network.

import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { defineConfig } from "hardhat/config";
import * as dotenv from "dotenv";

dotenv.config();

const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL;
const SEPOLIA_PRIVATE_KEY = process.env.SEPOLIA_PRIVATE_KEY;

if (!SEPOLIA_RPC_URL) {
  throw new Error("Missing SEPOLIA_RPC_URL in .env file");
}

if (!SEPOLIA_PRIVATE_KEY) {
  throw new Error("Missing SEPOLIA_PRIVATE_KEY in .env file");
}

export default defineConfig({
  plugins: [hardhatToolboxViemPlugin],

  solidity: {
    version: "0.8.28",
  },

  networks: {
    hardhatMainnet: {
      type: "edr-simulated",
      chainType: "l1",
    },

    sepolia: {
      type: "http",
      chainType: "l1",
      url: SEPOLIA_RPC_URL,
      accounts: [SEPOLIA_PRIVATE_KEY],
    },
  },
});

In this configuration:

  • SEPOLIA_RPC_URL is the RPC endpoint used to communicate with the Sepolia testnet.

  • SEPOLIA_PRIVATE_KEY is the private key of the wallet that will deploy the contract.

  • dotenv loads these variables from the .env file so they do not need to be written directly in the code.

Hardhat will now be able to connect to the Sepolia network when running deployment scripts.

Create the deployment script#

Now go inside the folder called ethereum/ then create a file called deploy.ts:

touch scripts/deploy.ts
code scripts/deploy.ts

Add the following code inside deploy.ts:

import { network } from "hardhat";

async function main() {

  const { viem } = await network.connect();

  const [deployer] = await viem.getWalletClients();

  console.log("Deploying contract with account:", deployer.account.address);

  const avatar = await viem.deployContract("Avatar");

  console.log("Avatar contract deployed to:", avatar.address);

}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

This script connects to the configured network and deploys the Avatar smart contract.

Deploy to Sepolia#

Now we can deploy the contract to the Sepolia test network.

Run the following command in the ethereum/ folder:

npx hardhat run scripts/deploy.ts --network sepolia

If the deployment is successful, the terminal will display these messages:

Deploying contract with account: 0x..... , this is the address of your wallet. You can check and compare with your address on metamask, it will be the same address.

Avatar contract deployed to: 0x... , this is the adress of the contract you just deployed.

Copy the second address. We will use it in the next step to mint our NFT.

Mint your NFT#

Create the mint script#

Now that the contract is deployed, we can create a script to mint our NFT.

Inside the scripts/ folder, create a file called mint.ts:

touch scripts/mint.ts
code scripts/mint.ts

Open the file and add the following code:

import { network } from "hardhat";
import { getAddress } from "viem";

async function main() {

  const { viem } = await network.connect();

  const [minter] = await viem.getWalletClients();

  console.log("Minting NFT with account:", minter.account.address);

  const contractAddress = getAddress("YOUR_CONTRACT_ADDRESS");
  const recipient = minter.account.address;
  const tokenURI = "ipfs://YOUR_METADATA_CID";

  const avatar = await viem.getContractAt("Avatar", contractAddress);

  const hash = await avatar.write.mint([recipient, tokenURI]);

  console.log("Mint transaction sent:", hash);
  console.log("Recipient:", recipient);
  console.log("Token URI:", tokenURI);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Replace:

  • YOUR_CONTRACT_ADDRESS with the address of the contract you deployed on Sepolia

  • YOUR_METADATA_CID with the CID of the json metadata file you uploaded to IPFS

Run the mint script#

From the ethereum/ directory, run:

npx hardhat run scripts/mint.ts --network sepolia

If everything goes well, after a couple of seconds, you should see a response like this in your terminal:

Minting NFT with account: 0x598b.........
Mint transaction sent: 0x1e953c8119655d.......
Recipient: 0x598bdd894......
Token URI: ipfs://bafkreidc3mwcm...........

Grab the hash property and go to https://sepolia.etherscan.io/tx/YOUR_HASH. You should see the minting transaction there!

Understanding Etherscan#

Etherscan is a blockchain explorer. It allows anyone to view transactions, smart contracts and wallet activity directly on the Ethereum network.

Since we deployed and minted our NFT on the Sepolia testnet, we use the Sepolia version of Etherscan:

https://sepolia.etherscan.io

When you open the transaction link, you will see several pieces of information:

  • Transaction Hash – the unique identifier of the transaction

  • From – the address that sent the transaction

  • To – the smart contract that received the transaction

  • Status – whether the transaction succeeded or failed

  • Block – the block in which the transaction was included

  • Gas Used – the computational cost of executing the transaction

Scrolling down the page, you can also see the input data of the transaction. This contains the encoded function call and the parameters that were sent to the smart contract.

If the contract has been verified on Etherscan, the explorer can decode this data and display the function that was called (for example mint) along with its parameters. Otherwise, the input data will appear as raw hexadecimal data.

Blockchain explorers like Etherscan are essential tools for developers because they allow us to inspect and verify what happened on the blockchain.

View the NFT in your wallet#

First dowload the mobile version of Metamask.

Then, log into your account. You should see an NFTs tab along with an add NFT button.

Click on the button and enter the address of your Smart Contract along with the ids that you have minted. In our case, we only minter 1 NFT so id should be 0.

Further content to get more familiar with Solidity#

If you want to go further into learning Solidity, there is a very nice tutorial called CryptoZombies. It guides you step by step through building simple blockchain games while learning the fundamentals of smart contract development.

Note that the tutorial uses older versions of Solidity, so some syntax and tooling may differ from what you used in this course. However, it remains an excellent resource for understanding the core concepts of smart contracts and blockchain development.