How to Create an NFT on Ethereum Tutorial

July 6, 2023


Estimated time to complete this guide: ~15 minutes

Even if you've been living under a rock, you'll have noticed that every major news outlet has been profiling the rise of NFTs.

With NFTs bringing blockchain into the public eye, now is an excellent opportunity to understand the hype yourself by publishing your own NFT (ERC-721 Token) on the Ethereum blockchain!

In this tutorial, we will walk through creating and deploying an ERC-721 smart contract on the Sepolia test network using Metamask, Solidity, Hardhat, Pinata and Alchemy (don’t fret if you don’t understand what any of this means yet— we will explain it). In Part II of this tutorial , we’ll go through how we can use our smart contract to mint an NFT from code, and in Part III we’ll cover how to How to View Your NFT in Your Mobile Wallet.

And of course, if you have questions at any point, don't hesitate to reach out in the Alchemy Discord!

Creating an NFT


Before you begin the steps in this tutorial, ensure you complete the following steps:

  • Install both Node.js(> 14) and npm on your local machine. To check your Node version, run the following command in your terminal:


node -v

Step 1: Create an Alchemy app

To create an Alchemy app, check out this video or follow the instructions below:

  1. From Alchemy's dashboard, hover over the Apps drop-down menu and choose Create App.
  2. Provide a Name and Description for your app.
  3. For Chain, select Ethereum and for Network select Sepolia.
  4. Click the Create App button.
Creating an Alchemy App
Creating an Alchemy App

Once you have created your app, click on your app's View Key button in the dashboard and save the API KEY. We will use this later.

Step 2: Create a Metamask Wallet

We need an Ethereum wallet to send and receive transactions. For this tutorial, we’ll use Metamask, a virtual wallet in the browser. If you want to understand more about how transactions on Ethereum work, check out this page from the Ethereum foundation.

You can download and create a Metamask account for free here. Once you have an account, make sure to switch to the "Sepolia Test Network” in the upper right (so that we’re not dealing with real money).

Step 3: Add SepoliaETH from a Faucet

In order to deploy our smart contract to the test network, we’ll need some fake SepoliaETH. The easiest way to acquire this is by using Alchemy's Sepolia faucet. Depending on traffic, the faucet may ask you to sign in with your Alchemy account.

If all goes well, you should see your SepoliaETH balance updated on Metamask.

Step 4: Create a Node Project

Let's create an empty node project. Navigate to your command line and type:


mkdir my-nft && cd my-nft
npm init -y

We are now in a good position to set up and install Hardhat, the industry standard Ethereum development environment.

Step 5: Create a Hardhat Project

Hardhat is a development environment to compile, deploy, test, and debug smart contracts. It helps developers create dApps locally before deploying them to a live chain.

In your terminal, run the following commands:


npm install --save-dev hardhat
npx hardhat

You should then see a welcome message and options on what you can do. Select Create a JavaScript project:


888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

👷 Welcome to Hardhat v2.12.2 👷‍

? What do you want to do? …
❯ Create a JavaScript project
 Create a TypeScript project
 Create an empty hardhat.config.js

Agree to all the defaults (project root, adding a .gitignore, and installing all sample project dependencies).

To check if everything works properly, run:


npx hardhat test

We now have our hardhat development environment successfully configured. Let us now install the OpenZeppelin contracts package. This will give us access to ERC721 implementations (the standard for NFTs) on top of which we will build our contract.


npm install @openzeppelin/contracts

Step 6: Write the smart contract

Open the project in your favorite editor (e.g. VSCode). We will use a language called Solidity to write our contract.

Navigate to the contracts folder and create a new file called MyNFT.sol. Add the following code to the file.


If you want to attach a price to the NFT through the smart contract check out this tutorial.


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

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

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

   constructor() ERC721("MyNFT", "NFT") {}

   function mintNFT(address recipient, string memory tokenURI)
       returns (uint256)

       uint256 newItemId = _tokenIds.current();
       _mint(recipient, newItemId);
       _setTokenURI(newItemId, tokenURI);

       return newItemId;

Make sure that the version defined above (^0.8.17) is the same as the version defined in the hardhat.config.js file. Now, Let's break down the code line by line.

In lines 5-7, our code inherits three OpenZepplin smart contract classes:

  • @openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol contains the implementation of the ERC721 standard, which our NFT smart contract will inherit. (To be a valid NFT, your smart contract must implement all the methods of the ERC721 standard.) To learn more about the inherited ERC721 functions, check out the interface definition here.
  • @openzeppelin/contracts/utils/Counters.solprovides counters that can only be incremented or decremented by one. Our smart contract uses a counter to keep track of the total number of NFTs minted and set the unique ID to our new NFT. Each NFT minted using a smart contract must be assigned a unique ID—here our unique ID is just determined by the total number of NFTs in existance. For example, the first NFT we mint with our smart contract has an ID of "1," our second NFT has an ID of "2," etc.
  • @openzeppelin/contracts/access/Ownable.sol sets up access control on our smart contract, so only the owner of the smart contract (you) can mint NFTs. Note, including access control is entirely a preference. If you'd like anyone to be able to mint an NFT using your smart contract, remove the word Ownable on line 9 and onlyOwner on line 16.

In Lines 9-27, we have our custom NFT smart contract, which is surprisingly short —it only contains a counter, a constructor, and single function! This is thanks to our inherited OpenZepplin contracts, which implement most of the methods we need to create an NFT, such as ownerOf (returns the owner of the NFT) and transferFrom(transfers ownership of the NFT).

On line 13, you'll notice we pass 2 strings, "MyNFT" and "NFT" into the ERC721 constructor. The first variable is the smart contract's name, and the second is its symbol. You can name each of these variables whatever you wish!

Finally, starting on line 15, we have our function mintNFT() that allows us to mint an NFT! You'll notice this function takes in two variables:

  • address recipient specifies the address that will receive your freshly minted NFT
  • string memory tokenURI is a string that should resolve to a JSON document that describes the NFT's metadata. An NFT's metadata is really what brings it to life, allowing it to have additional properties, such as a name, description, image, and other attributes. In part 2 of this tutorial, we will describe how to configure this metadata.

mintNFT calls some methods from the inherited ERC721 library, and ultimately returns a number that represents the ID of the freshly minted NFT.

Step 7: Connect Metamask & Alchemy to your project

Now that we've created a Metamask wallet, an Alchemy account, and a smart contract, it’s time to connect the three.

Every transaction sent from your virtual wallet requires a signature using your unique private key. To provide our program with this permission, we can safely store our private key (and Alchemy API key) in an environment file.

Install the dotenv package in your project directory by running:


npm install dotenv --save

Then, create a .env file in the root directory of our project, and add your Metamask private key and HTTP Alchemy API Key (from Step 1) to it.


Your .env file must be named .env ! Do not change the name to xx.env

Follow these instructions to export your private key from Metamask

Your .env should look like this:


API_URL = ""
PRIVATE_KEY = "your-metamask-private-key"

Step 8: Update hardhat.config.js

We’ve added several dependencies and plugins so far, now we need to update hardhat.config.js so that our project knows about all of them.

Replace the contents of hardhat.config.js with the following:


const { API_URL, PRIVATE_KEY } = process.env;

module.exports = {
 solidity: "0.8.17",
 defaultNetwork: "sepolia",
 networks: {
   hardhat: {},
   sepolia: {
     url: API_URL,
     accounts: [`0x${PRIVATE_KEY}`],

Step 9: Write the deployment script

Now that our contract is written and our configuration file is good to go, it’s time to write the contract deploy script.

Navigate to the scripts/ folder and replace the contents in the file deploy.js with the following:


async function main() {
  // Grab the contract factory
  const MyNFT = await ethers.getContractFactory("MyNFT");

  // Start deployment, returning a promise that resolves to a contract object
  const myNFT = await MyNFT.deploy(); // Instance of the contract
  console.log("Contract deployed to address:", myNFT.address);

 .then(() => process.exit(0))
 .catch(error => {

Hardhat does an amazing job of explaining what each of these lines of code does in their Contracts tutorial, we’ve adopted their explanations here.


const MyNFT = await ethers.getContractFactory("MyNFT");

A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts, so MyNFT here is a factory for instances of our NFT contract. When using the hardhat-ethers plugin ContractFactory and Contract instances are connected to the first signer by default.


const myNFT = await MyNFT.deploy();

Calling deploy() on a ContractFactory will start the deployment, and return a Promise that resolves to a Contract. This is the object that has a method for each of our smart contract functions.

Step 10: Deploy the contract

We’re finally ready to deploy our smart contract! Navigate back to the root of your project directory, and in the command line run:


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

You should then see something like:


Contract deployed to address: 0xA4766Ceb9E84a71D282A4CED9fB8Fe93C49b2Ff7

If we go to Sepolia Etherscan and search for our contract address we should be able to see that it has been deployed successfully. The transaction will look something like this:

Contract page on Sepolia Etherscan
Contract page on Sepolia Etherscan

The From address should match your Metamask account address and the To address will say Contract Creation. If we click into the transaction, we’ll see our contract address in the To field:

Contract Creation page on Sepolia Etherscan
Contract Creation page on Sepolia Etherscan

Yasssss! You just deployed your NFT smart contract to the Ethereum chain 🎉

To understand what’s going on under the hood, let’s navigate to the Explorer tab in our Alchemy dashboard. If you have multiple Alchemy apps make sure to filter by app and select “MyNFT”.

Alchemy Explorer

Here you’ll see a handful of JSON-RPC calls that Hardhat/Ethers made under the hood for us when we called the .deploy() function.

Two important ones to call out here are eth_sendRawTransaction, which is the request to actually write our smart contract onto the Sepolia chain, and eth_getTransactionByHash which is a request to read information about our transaction given the hash (a typical pattern when sending transactions).

That’s all for Part I of this tutorial. In Part II, we’ll actually interact with our smart contract by minting an NFT, and in Part III we'll explain how to view your NFT in Metamask! 🤑

More articles