7. How to Build an NFT Marketplace from Scratch
Welcome to Week 7 of the Road To Web3 series. This tutorial teaches you how to build your own NFT marketplace from scratch: frontend, data storage and smart contracts!
End to End Youtube Tutorial
Whether you sort by number of users or by volumes, NFT marketplaces are some of the biggest companies in Web3. For instance:
  • In Jan 2022, Opensea, Ethereum's largest NFT Marketplace sold ~2.5 million NFTs and traded $5 billion in volume.
  • In May 2022, Magic Eden, Solana's largest NFT Marketplace had ~11.3 million transactions and $200 million in volume.
Such scale is only achieved with great smart contracts and scalable infrastructure. So if you're a web3 dev looking to improve your web3 development skills, follow along with this tutorial on building an NFT marketplace using Alchemy, IPFS, Hardhat and ethers.js.
Some things to keep in mind:
  • The focus of this tutorial will be on building the smart contract and not building the frontend. However, the frontend code for an NFT marketplace is available on GitHub.
  • No backend or database is involved in this tutorial. A backend and database will only be needed when you start to archive data and integrate registration or login features.

Step 0: Sign up for an Alchemy account and create a new app

If you haven't already, sign up for your free Alchemy account.
You can then create a new app and create API keys from the app dashboard.
Check out this video on how to create an app:
Video tutorial on how to create a new Alchemy app
Or follow the written steps below:
  1. 1.
    Navigate to the "create app" button in the "Apps" tab
Screenshot of a sample dashboard
Fill in the details on the popup to get your new key. For this tutorial, you should choose "Ethereum" as the chain and "Goerli" as the test network.
Create app popup
You can also pull existing API keys by hovering over "Apps" and selecting one.
You can "View Key" here, as well as "Edit App" to whitelist specific domains, see several developer tools, and view analytics.
Screencast when creating the app

Step 1: Set up your MetaMask wallet for development

If you already have MetaMask with a Goerli address and at least 0.1 Goerli ETH on it, skip to Step 2.
If you do not have a Goerli address, connect MetaMask to the Goerli network, and then use a Goerli faucet to request Goerli ETH. You will need Goerli ETH to deploy smart contracts and upload NFTs to your NFT marketplace.
Make sure you add the below details when adding a new network:
Network Name: Goerli Test Network
RPC base URL: https://eth-goerli.alchemyapi.io/v2/{INSERT YOUR API KEY}
Chain ID: 5
Block Explorer URL: https://goerli.etherscan.io/
Symbol (Optional): ETH

Step 2: Set up the repository

To make it easy, we have uploaded the base code to the below GitHub repository. This code has the frontend all written but doesn't have a smart contract or integrations with frontend.
GitHub - alchemyplatform/RTW3-Week7-NFT-Marketplace: Road to Web3 Week7 tutorial on building an NFT Marketplace from Scratch
GitHub
Github Repository to be used for this tutorial
To clone the repository, run the below commands in your command prompt:
1
git clone https://github.com/alchemyplatform/RTW3-Week7-NFT-Marketplace.git
2
cd RTW3-Week7-NFT-Marketplace
3
npm install
4
npm start
Copied!
Note: The above GitHub repo is the base repo that you should build on top of.
Refer to this if you get stuck following along with the tutorial.

Step 3: Set up your environment variables and Hardhat config

Create a new .env file in the root of your project, which is right inside the RTW3-Week7-NFT-Marketplace folder, and add:
  • The Alchemy API URL that you created in Step 1
  • The private key of the MetaMask wallet that you will use for development
When you're done, your .env file should look like this:
1
REACT_APP_ALCHEMY_API_URL="<YOUR_API_URL>"
2
REACT_APP_PRIVATE_KEY="<YOUR_PRIVATE_KEY>"
Copied!
If not already installed, install dotenv in your root folder:
1
npm install dotenv --save
Copied!
dotenv helps you manage the environment variables that are mentioned in the .env file, making it easy for your project to access them.
WARNING: do not ship a production app with secrets in the .env file. This tutorial shows you how to upload to IPFS via your react client directly as a demonstration only.
When you are ready for production, you should re-factor your application to upload IPFS files using a backend service.
Read this for more context on React environment variables.
In your home directory, make sure the below code is added to your hardhat.config.js file:
1
require("@nomiclabs/hardhat-waffle");
2
require("@nomiclabs/hardhat-ethers");
3
const fs = require('fs');
4
// const infuraId = fs.readFileSync(".infuraid").toString().trim() || "";
5
require('dotenv').config();
6
​
7
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
8
const accounts = await hre.ethers.getSigners();
9
​
10
for (const account of accounts) {
11
console.log(account.address);
12
}
13
});
14
​
15
module.exports = {
16
defaultNetwork: "hardhat",
17
networks: {
18
hardhat: {
19
chainId: 1337
20
},
21
goerli: {
22
url: process.env.REACT_APP_ALCHEMY_API_URL,
23
accounts: [ process.env.REACT_APP_PRIVATE_KEY ]
24
}
25
},
26
solidity: {
27
version: "0.8.4",
28
settings: {
29
optimizer: {
30
enabled: true,
31
runs: 200
32
}
33
}
34
}
35
};
Copied!
Note: You may face issues in making the process.env work in the above hardhat config even after installing dotenv. In that case just paste the goerli URL and private key directly in this config. Make sure to not push it to github.

Step 4: Use PiΓ±ata to upload your data to IPFS

If you don't have a PiΓ±ata account, sign up for a free PiΓ±ata account and verify your email.

Create your PiΓ±ata API key

To create your PiΓ±ata key:
  • Navigate to https://pinata.cloud/keys
  • Select the "New Key" button at the top
  • Set the Admin widget as enabled
  • Name your key
Create your Pinata API key
You'll then be shown a popup with your API info. Copy this over to somewhere safe.
Make sure to save your API key and secret in a safe place
Now that the PiΓ±ata key is set up, add it to your project so you can use it.
Add your API key and secret so that the .env file now looks like below:
1
REACT_APP_ALCHEMY_API_URL="<YOUR_API_URL>"
2
REACT_APP_PRIVATE_KEY="<YOUR_PRIVATE_KEY>"
3
REACT_APP_PINATA_KEY="<YOUR_PINATA_KEY>"
4
REACT_APP_PINATA_SECRET="<YOUR_PINATA_SECRET>"
Copied!

Step 5: Understand the requirements

Below is the NFT marketplace that you will be making by the end of this tutorial.
We chose Dogs for this marketplace. Feel free to switch to any other photos you like!
Marketplace home page
Before we can dive deeper into writing code, let's go over separate pages to understand the feature set we need, both from a frontend and a smart contract perspective.

List your NFT page

List your NFT page
For any artist or creator, this is the page where they can list their NFT for sale on the marketplace.
As you can see, this takes in the following NFT attributes:
  • NFT Name
  • Description
  • Price (in ETH)
  • NFT Image
Once completed, this content will be uploaded to the NFT marketplace.
To make this happen, we need the following:
Smart Contract
Frontend
function createToken()
Input
  • an IPFS URL that has metadata
  • the listing price for the NFT
​
What it does?
  • Assigns a _tokenId to your NFT
  • Saves corresponding data to the marketplace contract
  • Emits a Listing Success event once done
See the implementation here.
Script that does the below:
​
  • Take inputs of all relevant details of the NFT
  • Upload NFT image to IPFS
  • Upload NFT metadata with an image link to IPFS
  • Send IPFS link and price to the createToken() function in the smart contract
  • Notify the user of a successful upload
​
You can find the implementation in src/contracts/SellNFT.js

Marketplace home page

NFT marketplace home page example.
This is the home page of the marketplace where all NFTs are listed.
To make this happen, we need:
Smart Contract
Frontend
function getAllNFTs()
Input
  • None
​
Output
  • A list of all NFTs currently on sale with their metadata
​
See the implementation here.
Script that does the following:
​
  • Fetch all NFTs on sale using the getAllNFTs() function in the smart contract
  • Display them in a grid format
  • Let users click through into an individual NFT to see more details
​
You can find the implementation in src/components/Marketplace.js , src/components/NFTPage.js and src/components/NFTTile.js

User Profile page

Profile Page
This is a user profile on the NFT marketplace and displays:
  • User's wallet address
  • Data about the user's owned NFTs
  • A grid view of all of those NFTs with details
To achieve this, we need:
Smart Contract
Frontend
  • function getMyNFTs() that returns all the NFTs a user has sold in the past.
​
The implementation can be found here.
Script that does below:
​
  • Fetch data using and getMyNFTs() from the smart contract
  • Analyze data to get aggregate numbers and statistics
  • Display data in the above format

Individual NFT Page

Landing page for an individual NFT on the NFT marketplace.
If you click on any NFT in the marketplace page or from the Profile page, this is the page that visitors will see. This page displays:
  • Metadata of the NFT
  • A "Buy this NFT" button which lets another user buy the NFT
To achieve this, we need:
Smart Contract
Frontend
A few functions:
  1. 1.
    A tokenURI function that returns the tokenURI for a tokenId. We then fetch the metadata for that tokenURI.
  2. 2.
    An executeSale() function that helps do the necessary checks and transfers ownership when a user clicks on the "Buy this NFT" button. The implementation can be found here​
Script that does the below:
​
  • Fetch tokenURI using tokenURI method
  • Fetch data from that IPFS tokenURI using axios
  • Display the data
  • Also, call the executeSale() function when the "Buy this NFT" button is clicked
Now you have a complete understanding of the features need to build an NFT marketplace.
Let's keep it going!
πŸŽ‰
​

Step 6: Write the Smart Contract

Let's start building an NFT marketplace! If you get confused, refer to the finished Smart Contract.

Add Imports

There is a file NFTMarketplace.sol in your contracts folder.
Add the below imports to the top of this file and add an empty class with a constructor:
1
//SPDX-License-Identifier: Unlicense
2
pragma solidity ^0.8.0;
3
​
4
//Console functions to help debug the smart contract just like in Javascript
5
import "hardhat/console.sol";
6
//OpenZeppelin's NFT Standard Contracts. We will extend functions from this in our implementation
7
import "@openzeppelin/contracts/utils/Counters.sol";
8
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
9
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
10
​
11
contract NFTMarketplace is ERC721URIStorage {
12
constructor() ERC721("NFTMarketplace", "NFTM") {
13
owner = payable(msg.sender);
14
}
15
}
Copied!
The code is explained in the comments.

Add Global Variables

Add the below Global variables to the top of your Smart Contract inside the class declaration:
1
using Counters for Counters.Counter;
2
//_tokenIds variable has the most recent minted tokenId
3
Counters.Counter private _tokenIds;
4
//Keeps track of the number of items sold on the marketplace
5
Counters.Counter private _itemsSold;
6
//owner is the contract address that created the smart contract
7
address payable owner;
8
//The fee charged by the marketplace to be allowed to list an NFT
9
uint256 listPrice = 0.01 ether;
10
​
11
//The structure to store info about a listed token
12
struct ListedToken {
13
uint256 tokenId;
14
address payable owner;
15
address payable seller;
16
uint256 price;
17
bool currentlyListed;
18
}
19
​
20
//the event emitted when a token is successfully listed
21
event TokenListedSuccess (
22
uint256 indexed tokenId,
23
address owner,
24
address seller,
25
uint256 price,
26
bool currentlyListed
27
);
28
​
29
//This mapping maps tokenId to token info and is helpful when retrieving details about a tokenId
30
mapping(uint256 => ListedToken) private idToListedToken;
31
​
Copied!
  • _tokenIds : This is the latest token ID that corresponds to an NFT minted with this smart contract. tokenIDs map to tokenURI which is the URL that contains the metadata of the corresponding NFT
  • _itemsSold : Is a count of the number of items sold on the marketplace
  • owner : This is the owner of smart contract. The only address that can issue a withdraw request.
  • listPrice : The price (in ETH) any user needs to pay to list their NFT on the marketplace
  • ListedToken : A solidity struct (similar to Javascript object) dictating the format an NFT's data is stored in
  • TokenListedSuccess : Event emitted when a token is successfully listed
  • idToListedToken : It is the mapping of all existing tokenId's to the corresponding NFT token

createToken and createListedToken

This function turns a tokenURI (URL with metadata) into an actual NFT on-chain, with details stored in the smart contract. This is useful for the List your NFT page.
Add the below functions inside your contract class right under your Global variable declaration:
1
//The first time a token is created, it is listed here
2
function createToken(string memory tokenURI, uint256 price) public payable returns (uint) {
3
//Increment the tokenId counter, which is keeping track of the number of minted NFTs
4
_tokenIds.increment();
5
uint256 newTokenId = _tokenIds.current();
6
​
7
//Mint the NFT with tokenId newTokenId to the address who called createToken
8
_safeMint(msg.sender, newTokenId);
9
​
10
//Map the tokenId to the tokenURI (which is an IPFS URL with the NFT metadata)
11
_setTokenURI(newTokenId, tokenURI);
12
​
13
//Helper function to update Global variables and emit an event
14
createListedToken(newTokenId, price);
15
​
16
return newTokenId;
17
}
18
​
19
function createListedToken(uint256 tokenId, uint256 price) private {
20
//Make sure the sender sent enough ETH to pay for listing
21
require(msg.value == listPrice, "Hopefully sending the correct price");
22
//Just sanity check
23
require(price > 0, "Make sure the price isn't negative");
24
​
25
//Update the mapping of tokenId's to Token details, useful for retrieval functions
26
idToListedToken[tokenId] = ListedToken(
27
tokenId,
28
payable(address(this)),
29
payable(msg.sender),
30
price,
31
true
32
);
33
​
34
_transfer(msg.sender, address(this), tokenId);
35
//Emit the event for successful transfer. The frontend parses this message and updates the end user
36
emit TokenListedSuccess(
37
tokenId,
38
address(this),
39
msg.sender,
40
price,
41
true
42
);
43
}
Copied!
The relevance of every line of code is mentioned in comments. Take 2 mins to go through it.

getAllNFTs

This function returns all the "active" NFTs (currently on sale) in the marketplace. This is useful for the marketplace home page.
Add the below function in your contract class, right below the createListedToken function:
1
//This will return all the NFTs currently listed to be sold on the marketplace
2
function getAllNFTs() public view returns (ListedToken[] memory) {
3
uint nftCount = _tokenIds.current();
4
ListedToken[] memory tokens = new ListedToken[](nftCount);
5
uint currentIndex = 0;
6
​
7
//at the moment currentlyListed is true for all, if it becomes false in the future we will
8
//filter out currentlyListed == false over here
9
for(uint i=0;i<nftCount;i++)
10
{
11
uint currentId = i + 1;
12
ListedToken storage currentItem = idToListedToken[currentId];
13
tokens[currentIndex] = currentItem;
14
currentIndex += 1;
15
}
16
//the array 'tokens' has the list of all NFTs in the marketplace
17
return tokens;
18
}
Copied!
The relevance of every line of code is mentioned in the comments.

getMyNFTs

This function returns all the "active" NFTs (currently on sale) in the marketplace, that the current logged in user owns. This is useful for the profile page.
Add the below function in your contract class, right below the getAllNFTs function:
1
//Returns all the NFTs that the current user is owner or seller in
2
function getMyNFTs() public view returns (ListedToken[] memory) {
3
uint totalItemCount = _tokenIds.current();
4
uint itemCount = 0;
5
uint currentIndex = 0;
6
7
//Important to get a count of all the NFTs that belong to the user before we can make an array for them
8
for(uint i=0; i < totalItemCount; i++)
9
{
10
if(idToListedToken[i+1].owner == msg.sender || idToListedToken[i+1].seller == msg.sender){
11
itemCount += 1;
12
}
13
}
14
​
15
//Once you have the count of relevant NFTs, create an array then store all the NFTs in it
16
ListedToken[] memory items = new ListedToken[](itemCount);
17
for(uint i=0; i < totalItemCount; i++) {
18
if(idToListedToken[i+1].owner == msg.sender || idToListedToken[i+1].seller == msg.sender) {
19
uint currentId = i+1;
20
ListedToken storage currentItem = idToListedToken[currentId];
21
items[currentIndex] = currentItem;
22
currentIndex += 1;
23
}
24
}
25
return items;
26
}
Copied!
The relevance of every line of code is mentioned in the comments.

executeSale

When a user clicks "Buy this NFT" on the profile page, the executeSale function is triggered.
If the user has paid enough ETH equal to the price of the NFT, the NFT gets transferred to the new address and the proceeds of the sale are sent to the seller.
Add the below function to your smart contract:
1
function executeSale(uint256 tokenId) public payable {
2
uint price = idToListedToken[tokenId].price;
3
address seller = idToListedToken[tokenId].seller;
4
require(msg.value == price, "Please submit the asking price in order to complete the purchase");
5
​
6
//update the details of the token
7
idToListedToken[tokenId].currentlyListed = true;
8
idToListedToken[tokenId].seller = payable(msg.sender);
9
_itemsSold.increment();
10
​
11
//Actually transfer the token to the new owner
12
_transfer(address(this), msg.sender, tokenId);
13
//approve the marketplace to sell NFTs on your behalf
14
approve(address(this), tokenId);
15
​
16
//Transfer the listing fee to the marketplace creator
17
payable(owner).transfer(listPrice);
18
//Transfer the proceeds from the sale to the seller of the NFT
19
payable(seller).transfer(msg.value);
20
}
Copied!

Other Helper functions

Below are other helper functions, which are good to have in your smart contracts for testing and would be helpful if you decide to extend more functionalities.
Feel free to add these anywhere in your class:
1
function updateListPrice(uint256 _listPrice) public payable {
2
require(owner == msg.sender, "Only owner can update listing price");
3
listPrice = _listPrice;
4
}
5
​
6
function getListPrice() public view returns (uint256) {
7
return listPrice;
8
}
9
​
10
function getLatestIdToListedToken() public view returns (ListedToken memory) {
11
uint256 currentTokenId = _tokenIds.current();
12
return idToListedToken[currentTokenId];
13
}
14
​
15
function getListedTokenForId(uint256 tokenId) public view returns (ListedToken memory) {
16
return idToListedToken[tokenId];
17
}
18
​
19
function getCurrentToken() public view returns (uint256) {
20
return _tokenIds.current();
21
}
Copied!
After doing all of the above, below is what your smart contract should look like:
1
//SPDX-License-Identifier: Unlicense
2
pragma solidity ^0.8.0;
3
​
4
import "hardhat/console.sol";
5
import "@openzeppelin/contracts/utils/Counters.sol";
6
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
7
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
8
​
9
contract NFTMarketplace is ERC721URIStorage {
10
​
11
using Counters for Counters.Counter;
12
//_tokenIds variable has the most recent minted tokenId
13
Counters.Counter private _tokenIds;
14
//Keeps track of the number of items sold on the marketplace
15
Counters.Counter private _itemsSold;
16
//owner is the contract address that created the smart contract
17
address payable owner;
18
//The fee charged by the marketplace to be allowed to list an NFT
19
uint256 listPrice = 0.01 ether;
20
​
21
//The structure to store info about a listed token
22
struct ListedToken {
23
uint256 tokenId;
24
address payable owner;
25
address payable seller;
26
uint256 price;
27
bool currentlyListed;
28
}
29
​
30
//the event emitted when a token is successfully listed
31
event TokenListedSuccess (
32
uint256 indexed tokenId,
33
address owner,
34
address seller,
35
uint256 price,
36
bool currentlyListed
37
);
38
​
39
//This mapping maps tokenId to token info and is helpful when retrieving details about a tokenId
40
mapping(uint256 => ListedToken) private idToListedToken;
41
​
42
constructor() ERC721("NFTMarketplace", "NFTM") {
43
owner = payable(msg.sender);
44
}
45
​
46
function updateListPrice(uint256 _listPrice) public payable {
47
require(owner == msg.sender, "Only owner can update listing price");
48
listPrice = _listPrice;
49
}
50
​
51
function getListPrice() public view returns (uint256) {
52
return listPrice;
53
}
54
​
55
function getLatestIdToListedToken() public view returns (ListedToken memory) {
56
uint256 currentTokenId = _tokenIds.current();
57
return idToListedToken[currentTokenId];
58
}
59
​
60
function getListedTokenForId(uint256 tokenId) public view returns (ListedToken memory) {
61
return idToListedToken[tokenId];
62
}
63
​
64
function getCurrentToken() public view returns (uint256) {
65
return _tokenIds.current();
66
}
67
​
68
//The first time a token is created, it is listed here
69
function createToken(string memory tokenURI, uint256 price) public payable returns (uint) {
70
//Increment the tokenId counter, which is keeping track of the number of minted NFTs
71
_tokenIds.increment();
72
uint256 newTokenId = _tokenIds.current();
73
​
74
//Mint the NFT with tokenId newTokenId to the address who called createToken
75
_safeMint(msg.sender, newTokenId);
76
​
77
//Map the tokenId to the tokenURI (which is an IPFS URL with the NFT metadata)
78
_setTokenURI(newTokenId, tokenURI);
79
​
80
//Helper function to update Global variables and emit an event
81
createListedToken(newTokenId, price);
82
​
83
return newTokenId;
84
}
85
​
86
function createListedToken(uint256 tokenId, uint256 price) private {
87
//Make sure the sender sent enough ETH to pay for listing
88
require(msg.value == listPrice, "Hopefully sending the correct price");
89
//Just sanity check
90
require(price > 0, "Make sure the price isn't negative");
91
​
92
//Update the mapping of tokenId's to Token details, useful for retrieval functions
93
idToListedToken[tokenId] = ListedToken(
94
tokenId,
95
payable(address(this)),
96
payable(msg.sender),
97
price,
98
true
99
);
100
​
101
_transfer(msg.sender, address(this), tokenId);
102
//Emit the event for successful transfer. The frontend parses this message and updates the end user
103
emit TokenListedSuccess(
104
tokenId,
105
address(this),
106
msg.sender,
107
price,
108
true
109
);
110
}
111
112
//This will return all the NFTs currently listed to be sold on the marketplace
113
function getAllNFTs() public view returns (ListedToken[] memory) {
114
uint nftCount = _tokenIds.current();
115
ListedToken[] memory tokens = new ListedToken[](nftCount);
116
uint currentIndex = 0;
117
​
118
//at the moment currentlyListed is true for all, if it becomes false in the future we will
119
//filter out currentlyListed == false over here
120
for(uint i=0;i<nftCount;i++)
121
{
122
uint currentId = i + 1;
123
ListedToken storage currentItem = idToListedToken[currentId];
124
tokens[currentIndex] = currentItem;
125
currentIndex += 1;
126
}
127
//the array 'tokens' has the list of all NFTs in the marketplace
128
return tokens;
129
}
130
131
//Returns all the NFTs that the current user is owner or seller in
132
function getMyNFTs() public view returns (ListedToken[] memory) {
133
uint totalItemCount = _tokenIds.current();
134
uint itemCount = 0;
135
uint currentIndex = 0;
136
137
//Important to get a count of all the NFTs that belong to the user before we can make an array for them
138
for(uint i=0; i < totalItemCount; i++)
139
{
140
if(idToListedToken[i+1].owner == msg.sender || idToListedToken[i+1].seller == msg.sender){
141
itemCount += 1;
142
}
143
}
144
​
145
//Once you have the count of relevant NFTs, create an array then store all the NFTs in it
146
ListedToken[] memory items = new ListedToken[](itemCount);
147
for(uint i=0; i < totalItemCount; i++) {
148
if(idToListedToken[i+1].owner == msg.sender || idToListedToken[i+1].seller == msg.sender) {
149
uint currentId = i+1;
150
ListedToken storage currentItem = idToListedToken[currentId];
151
items[currentIndex] = currentItem;
152
currentIndex += 1;
153
}
154
}
155
return items;
156
}
157
​
158
function executeSale(uint256 tokenId) public payable {
159
uint price = idToListedToken[tokenId].price;
160
address seller = idToListedToken[tokenId].seller;
161
require(msg.value == price, "Please submit the asking price in order to complete the purchase");
162
​
163
//update the details of the token
164
idToListedToken[tokenId].currentlyListed = true;
165
idToListedToken[tokenId].seller = payable(msg.sender);
166
_itemsSold.increment();
167
​
168
//Actually transfer the token to the new owner
169
_transfer(address(this), msg.sender, tokenId);
170
//approve the marketplace to sell NFTs on your behalf
171
approve(address(this), tokenId);
172
​
173
//Transfer the listing fee to the marketplace creator
174
payable(owner).transfer(listPrice);
175
//Transfer the proceeds from the sale to the seller of the NFT
176
payable(seller).transfer(msg.value);
177
}
178
​
179
//We might add a resell token function in the future
180
//In that case, tokens won't be listed by default but users can send a request to actually list a token
181
//Currently NFTs are listed by default
182
}
Copied!

Step 7: Deploy the smart contract on Goerli

Good job coding through that huge smart contract! You're awesome!
πŸ’–
​
Now we need to deploy the contract. Alchemy recommends the Goerli testnet since Rinkeby will be deprecated with the incoming Ethereum merge.
There's a script named deploy.js within the scripts/ folder. In that file, paste this code:
1
const { ethers } = require("hardhat");
2
const hre = require("hardhat");
3
const fs = require("fs");
4
​
5
async function main() {
6
//get the signer that we will use to deploy
7
const [deployer] = await ethers.getSigners();
8
9
//Get the NFTMarketplace smart contract object and deploy it
10
const Marketplace = await hre.ethers.getContractFactory("NFTMarketplace");
11
const marketplace = await Marketplace.deploy();
12
​
13
await marketplace.deployed();
14
15
//Pull the address and ABI out while you deploy, since that will be key in interacting with the smart contract later
16
const data = {
17
address: marketplace.address,
18
abi: JSON.parse(marketplace.interface.format('json'))
19
}
20
​
21
//This writes the ABI and address to the marketplace.json
22
//This data is then used by frontend files to connect with the smart contract
23
fs.writeFileSync('./src/Marketplace.json', JSON.stringify(data))
24
}
25
​
26
main()
27
.then(() => process.exit(0))
28
.catch((error) => {
29
console.error(error);
30
process.exit(1);
31
});
Copied!
Hit save.
Then open your command prompt and execute the below command:
1
npx hardhat run --network rinkeby scripts/deploy.js
Copied!
Make sure you've updated your hardhat.config.js as per Step 3 to be able to deploy the smart contract.
If you don't see any errors or warnings, your smart contract was successfully deployed!
You should be able to see the address it was deployed to and the ABI of the smart contract in src/Marketplace.json

Step 8: Add the functions to upload NFT metadata to PiΓ±ata

In your home directory, in the empty file named pinata.js add this code:
1
//require('dotenv').config();
2
const key = process.env.REACT_APP_PINATA_KEY;
3
const secret = process.env.REACT_APP_PINATA_SECRET;
4
​
5
const axios = require('axios');
6
const FormData = require('form-data');
7
​
8
export const uploadJSONToIPFS = async(JSONBody) => {
9
const url = `https://api.pinata.cloud/pinning/pinJSONToIPFS`;
10
//making axios POST request to Pinata ⬇️
11
return axios
12
.post(url, JSONBody, {
13
headers: {
14
pinata_api_key: key,
15
pinata_secret_api_key: secret,
16
}
17
})
18
.then(function (response) {
19
return {
20
success: true,
21
pinataURL: "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash
22
};
23
})
24
.catch(function (error) {
25
console.log(error)
26
return {
27
success: false,
28
message: error.message,
29
}
30
​
31
});
32
};
33
​
34
export const uploadFileToIPFS = async(file) => {
35
const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
36
//making axios POST request to Pinata ⬇️
37
38
let data = new FormData();
39
data.append('file', file);
40
​
41
const metadata = JSON.stringify({
42
name: 'testname',
43
keyvalues: {
44
exampleKey: 'exampleValue'
45
}
46
});
47
data.append('pinataMetadata', metadata);
48
​
49
//pinataOptions are optional
50
const pinataOptions = JSON.stringify({
51
cidVersion: 0,
52
customPinPolicy: {
53
regions: [
54
{
55
id: 'FRA1',
56
desiredReplicationCount: 1
57
},
58
{
59
id: 'NYC1',
60
desiredReplicationCount: 2
61
}
62
]
63
}
64
});
65
data.append('pinataOptions', pinataOptions);
66
​
67
return axios
68
.post(url, data, {
69
maxBodyLength: 'Infinity',
70
headers: {
71
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
72
pinata_api_key: key,
73
pinata_secret_api_key: secret,
74
}
75
})
76
.then(function (response) {
77
console.log("image uploaded", response.data.IpfsHash)
78
return {
79
success: true,
80
pinataURL: "https://gateway.pinata.cloud/ipfs/" + response.data.IpfsHash
81
};
82
})
83
.catch(function (error) {
84
console.log(error)
85
return {
86
success: false,
87
message: error.message,
88
}
89
​
90
});
91
};
Copied!
The two functions are:
1. uploadFileToIPFS()
This function uploads the NFT image file to IPFS and then returns an IPFS URL which can be queried to obtain the image.
2.uploadJSONToIPFS(JSON)
This functions takes the entire JSON to be uploaded as an input and uploads it to IPFS. The value returned by the function is an IPFS URI which can be queried to get the metadata. This URI is super helpful when we want to retrieve the NFT metadata info later.

Step 9: Integrate the Frontend with the Smart Contract

For the platform to work seamlessly, integrate the frontend with functions from the smart contract.

A note about the frontend:

Building the frontend for this is a huge task. While we'd love to teach it all here in this tutorial itself to our devs, we do not want to overwhelm you. Hence, the Github repository has all the frontend code with separate components for every separate page. Every frontend component like src/components/SellNFT.js for instance,
  1. 1.
    Has a function that creates a provider, signer and a contract object
  2. 2.
    Fetches relevant data from the smart contract
  3. 3.
    Fetches relevant data from IPFS via axios
  4. 4.
    has a return where it returns the JSX/HTML for the page
While we are skipping talking about 4 in this tutorial, we still cover items 1, 2 & 3. We will release a future tutorial on item 4 and will keep this page updated.

src/components/SellNFT.js

The most important integration will be in src/components/SellNFT.js where we do 3 steps: