What you will be building, see demo on the Rinkeby test network and git repo here…
Introduction
Web3 is the internet's future, and we must all learn and embrace this technology. Its use-cases continue to emerge and have a positive impact on the world.
One magnificent application of web3 technology is the creation of non-fungible tokens (NFTs), which are a viable solution for digitalizing assets while retaining ownership rights.
This tutorial will teach you how to create a profitable and well-designed NFT marketplace with chat functionality.
Book your private classes with me if you need someone to help you learn web3 development faster.
With that said, let's get started...
Check out my YouTube channel for FREE web3 tutorials now.
Prerequisite
You will need the following tools installed to successfully crush this build:
- Node
- Ganache-Cli
- Truffle
- React
- Infuria
- Tailwind CSS
- CometChat SDK
- Metamask
- Yarn
Installing Dependencies
NodeJs Installation
Check that NodeJs is already installed on your machine, and if not, install it from HERE. Run the code again on the terminal to ensure it is installed.
Yarn, Ganache-cli and Truffle Installation
Run the commands below in your terminal to install these critical packages globally.
npm i -g yarn
npm i -g truffle
npm i -g ganache-cli
To confirm installation, enter the following code into the terminal.
yarn --version && ganache-cli --version && truffle version
Cloning Web3 Starter Project
Clone the web 3.0 starter project using the commands below. This ensures that we're all on the same page and using the same software.
git clone https://github.com/Daltonic/timelessNFT
Excellent, please replace the **package.json**
file with the following:
After replacing the packages as directed, run **yarn install**
on your terminal to load all of the packages with the specified versions.
Configuring CometChat SDK
Follow the steps below to configure the CometChat SDK; at the end, you must save these keys as an environment variable.
STEP 1:
Head to CometChat Dashboard and create an account.
STEP 2:
Log in to the CometChat dashboard, only after registering.
STEP 3:
From the dashboard, add a new app called timelessNFT.
STEP 4:
Select the app you just created from the list.
STEP 5:
From the Quick Start copy the APP_ID
, REGION
, and AUTH_KEY
, to your .env
file. See the image and code snippet.
Replace the REACT_COMET_CHAT
placeholders keys with their appropriate values.
REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
Configuring Infuria App
STEP 1:
Head to Infuria, create an account.
STEP 2:
From the dashboard create a new project.
STEP 3:
Copy the Rinkeby
test network WebSocket endpoint URL to your .env
file.
After that, enter your Metamask secret phrase and preferred account's private key. If you followed the instructions correctly, your environment variables should now look like this.
ENDPOINT_URL=***************************
DEPLOYER_KEY=**********************
REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
See the section below if you don't know how to access your private key.
Accessing Your Metamask Private Key
STEP 1:
Make sure Rinkeby is selected as the test network in your Metamask browser extension. Then, on the preferred account, click the vertical dotted line and choose account details. Please see the image below.
STEP 2:
Enter your password on the field provided and click the confirm button, this will enable you to access your account private key.
STEP 3:
Click on "export private key" to see your private key. Make sure you never expose your keys on a public page such as Github
. That is why we are appending it as an environment variable.
STEP 4:
Copy your private key to your .env
file. See the image and code snippet below:
ENDPOINT_URL=***************************
SECRET_KEY=******************
DEPLOYER_KEY=**********************
REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
As for your SECRET_KEY
, you are required to paste your Metamask
secret phrase in the space provided in the environment file.
The TimlessNFT Smart Contract
Here is the complete smart contract code; Let’s go over all of the functions and variables one by one.
Code imports and contract information
In the code below, we informed the solidity compiler of the license identifier and compiler versions qualified to compile this code.
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "./ERC721.sol";
import "./ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract TimelessNFT is ERC721Enumerable, Ownable {
// codes goes in here...
}
Also, this smart contract makes use of some openzepplin
'
s
ERC721 smart contracts. You have to make sure that you put them in the same directory as seen in the image below:
Visit this link and download these smart contracts as shown in the image above.
State variables declarations
using Strings for uint256;
mapping(string => uint8) existingURIs;
mapping(uint256 => address) public holderOf;
address public artist;
uint256 public royalityFee;
uint256 public supply = 0;
uint256 public totalTx = 0;
uint256 public cost = 0.01 ether;
We specified that we’re using the string library for performing uint
to string
operations. Next, we also declared mappings for recording minted NFT artworks and also for knowing the current owner of a token.
Then we specified the other essential variables for capturing the artist account, the royalty fees, the current supply, total transactions that have been made on the platform, and the mint cost of an NFT.
Setting up events and structures
event Sale(
uint256 id,
address indexed owner,
uint256 cost,
string metadataURI,
uint256 timestamp
);
struct TransactionStruct {
uint256 id;
address owner;
uint256 cost;
string title;
string description;
string metadataURI;
uint256 timestamp;
}
TransactionStruct[] transactions;
TransactionStruct[] minted;
In the preceding code, we have a sales event that emits data from any transaction that occurs on the smart contract, whether it is on minting or NFT transfer.
We designed a transaction structure to collect data about minted or transferred NFTs. Using the transaction structure we defined, we created two variables called transactions
and minted
.
Initializing the constructor
constructor(
string memory _name,
string memory _symbol,
uint256 _royalityFee,
address _artist
) ERC721(_name, _symbol) {
royalityFee = _royalityFee;
artist = _artist;
}
The constructor takes in four parameters for initializing the smart contract. A token name, symbol, an artist account, and a royalty fee per transaction. The token name and symbol are then passed into the ERC721
smart contract during deployment.
The mint function algorithm
function payToMint(
string memory title,
string memory description,
string memory metadataURI,
uint256 salesPrice
) external payable {
require(msg.value >= cost, "Ether too low for minting!");
require(existingURIs[metadataURI] == 0, "This NFT is already minted!");
require(msg.sender != owner(), "Sales not allowed!");
uint256 royality = (msg.value * royalityFee) / 100;
payTo(artist, royality);
payTo(owner(), (msg.value - royality));
supply++;
minted.push(
TransactionStruct(
supply,
msg.sender,
salesPrice,
title,
description,
metadataURI,
block.timestamp
)
);
emit Sale(
supply,
msg.sender,
msg.value,
metadataURI,
block.timestamp
);
_safeMint(msg.sender, supply);
existingURIs[metadataURI] = 1;
holderOf[supply] = msg.sender;
}
The above function is responsible for minting new tokens on the smart contract. The caller of this method must provide four parameters which include; an NFT title, description, metadata URI, and the selling price of the NFT after minting.
Validations are carried out to ensure that the NFT is minting and are done accordingly with payments made for each minting. Also, the validation ensures that each artwork is uniquely linked with a token, and no other token bears the same artwork. Lastly for the validation, we made sure that the caller of this method is not the deployer of the smart contract, this is to ensure we don’t mix up things too bad.
Next in the function is the payment sharing rule. The royalty percentage goes to the artist and the rest of the ethers goes to the owner.
Afterward, we recorded that NFT inside the minted array and emitted a sales event. Lastly, we minted the NFT while recording the caller’s address as the owner of the token.
The NFT transfer function algorithm
function payToBuy(uint256 id) external payable {
require(msg.value >= minted[id - 1].cost, "Ether too low for purchase!");
require(msg.sender != minted[id - 1].owner, "Operation Not Allowed!");
uint256 royality = (msg.value * royalityFee) / 100;
payTo(artist, royality);
payTo(minted[id - 1].owner, (msg.value - royality));
totalTx++;
transactions.push(
TransactionStruct(
totalTx,
msg.sender,
msg.value,
minted[id - 1].title,
minted[id - 1].description,
minted[id - 1].metadataURI,
block.timestamp
)
);
emit Sale(
totalTx,
msg.sender,
msg.value,
minted[id - 1].metadataURI,
block.timestamp
);
minted[id - 1].owner = msg.sender;
}
The above function takes an NFT id and makes a purchase of the NFT according to the set price by the minter (owner).
Necessary validations are done to obstruct owners from buying their NFTs and others from buying with zero ethers.
Next, a royalty fee is sent to the artist account and the current owner of the NFT gets to keep the rest.
Each token transfer is recorded in a transactions array to keep track of all transactions done on the platform.
Afterward, a sales event is again emitted for this purchase to enrich the logged data on the EVM.
Other Essential functions
// changes the price of an NFT
function changePrice(uint256 id, uint256 newPrice) external returns (bool) {
require(newPrice > 0 ether, "Ether too low!");
require(msg.sender == minted[id - 1].owner, "Operation Not Allowed!");
minted[id - 1].cost = newPrice;
return true;
}
// sends ethers to a specific account
function payTo(address to, uint256 amount) internal {
(bool success, ) = payable(to).call{value: amount}("");
require(success);
}
// returns all minted NFTs
function getAllNFTs() external view returns (TransactionStruct[] memory) {
return minted;
}
// returns a specific NFT by token id
function getNFT(uint256 id) external view returns (TransactionStruct memory) {
return minted[id - 1];
}
// returns all transactions
function getAllTransactions() external view returns (TransactionStruct[] memory) {
return transactions;
}
And there you have it for developing the smart contract, we will next dive into building the UI components with ReactJs.
Configuring the Deployment Script
One more thing to do with for the smart contract is to configure the deployment script.
On the project head to the migrations folder >> 2_deploy_contracts.js and update it with the code snippet below.
const TimelessNFT = artifacts.require('TimelessNFT')
module.exports = async (deployer) => {
const accounts = await web3.eth.getAccounts()
await deployer.deploy(
TimelessNFT,
'Timeless NFTs',
'TNT', 10, accounts[1]
)
}
Superb, we just finished the smart contract for our application; now it's time to get started on the DApp interface. If you need a private tutor to help you learn smart contract development, book your classes with me.
Developing the Frontend
The front end is made up of numerous components and parts. All of the components, views, and peripherals will be created by us.
Header Component
This component was created with tailwind CSS and uses the pink Connect Wallet
button to access the Metamask wallet. The codes below demonstrate the programming.
Hero Component
This component is responsible for displaying the connected wallet and also for launching the modal used for creating a new NFT. Additionally, it is responsible for signing in or up users for one-on-one chats with a seller of an NFT. Here is the code responsible for these actions.
Artworks component
This component is responsible for rendering the list of NFTs minted on the platform using the beautifully crafted tailwind CSS cards. Each card has an NFT image, title, description, price, and owner. See the codes below for its implementation.
Transactions Component
This component is responsible for rendering all the transactions that took place in our smart contract. A transaction for example would be Alison purchasing an NFT from Duke. This purchase will be captured in this component as a transaction. See the snippet below.
Footer Component
This component simply displays some beautiful links at the bottom of the page, it doesn’t do much when it comes to functionalities but complements the user interface. Its codes are written below.
Fantastic, that is it for the obvious components, let’s include the hidden components that are only invoked via a modal interface.
CreateNFT Component
This component is saddled with the duty of minting new NFTs by supplying an image, title, price, and description. Once the Mint Now
button is clicked, the image is uploaded to IPFS (Inter Planetary File System) and an image URL is returned.
The returned image URL along with the NFT data supplied in the form is sent to our smart contract for minting, immediately after the user authorizes the transaction with their Metamask wallet.
On completion of the transaction, the NFT is then listed among the artworks, and interested buyers can then purchase them and even change their prices. See the code below for details.
ShowNFT Component
This component displays more information about a specific NFT, offering the owner a button to change the price, and the buyer a button to either purchase the NFT or chat with the seller. See the code below for more details.
UpdateNFT Component
This component is tasked with changing the price of the NFT. This action can only be performed by the owner of the NFT. While this option is available, it will take some gas fee to effect these changes. Once an NFT exchanges a hand with another buyer, the new owner might decide to increase the price, and that’s why this option was made available. See the code snippet below.
ChatList Component
This component reveals the recent chats a user has made with a seller or buyer on the platform. The component also captures the last message that was sent in their conversation. A click on each conversation will lead to the chat interface. See the code below.
Chat Component
This component is responsible for engaging two users in a one-on-one chat. The image above shows a conversation between a buyer and a seller on the platform, from two different browsers. See the code below for its implementation.
Nice, now that we’ve included those fantastic components, let's finish it up with these last two.
Loading Component
This component simply displays the current activity and status when a transaction is in process. See the code below.
The App Component
This file bundles up the above component discussed in this work. This is just how a ReactJs architecture works. See the codes below.
Fantastic, we’ve just completed the integration of the various components, let’s seal it up with the other parts of this project.
Other Essential Files
This application utilizes a state management store, a CometChat SDK, and a contract service file. Let’s take a look at them one after the other.
The Store
This state management file uses the react-hooks-global-state
npm package. It is simple, fast, and easier than Redux. All the global variables and functions used in this app were created in this store.
At the root of the project, go to the src
directory and create a folder named store
. Now, within this store folder, create a file called index.js
and paste the codes below inside of it.
The CometChat Service
This file contains all the essential functions to communicate with the CometChat SDK. See the codes below.
The Contract Service File
This file contains all the functions and procedures responsible for interacting with the smart contract on the blockchain using the Web3 library. See the codes below.
Project Assets
Download this logo and include it inside the assets folder in your root directory. And with that, you’ve successfully included all that is needed to run this application.
Starting up the server
To proceed with this step, migrate the smart contract to the Web so you can interact with it. Run the following code on your terminal.
truffle migrate --network rinkeby
The above code will ship your smart contract to the server using the infuria RPC.
You can also set up a local blockchain using the ganache-cli server we set up at the beginning of this tutorial. Simply run the code below to ship it to your local blockchain server if you prefer that way.
Open one terminal run **ganache-cli -d**
and on a different terminal run **truffle migrate**
or **truffle deploy**
.
Note, if you are using ganache-cli
as your EVM, you must also add the localhost server to Metamask, and import the private keys generated by ganache. See Starting Up the Development Environment for guidance.
If you need my help resolving issues on your project, consult me on this page.
Now, run yarn start
to boot up your react application. And there you have it with this build on the NFT marketplace.
Watch my FREE web3 tutorials on YouTube now.
Conclusion
We’ve come to the finish line of this NFT build, I know you’ve gotten a ton of value building along with me.
Whatever level you are, if you want to grow faster in your web3 development skills, get into my private class.
Till next time, keep crushing it!
About the Author
Gospel Darlington is a full-stack blockchain developer with 6+
years of experience in the software development industry.
By combining Software Development, writing, and teaching, he demonstrates how to build decentralized applications on EVM-compatible blockchain networks.
His stacks include JavaScript
, React
, Vue
, Angular
, Node
, React Native
, NextJs
, Solidity
, and more.
For more information about him, kindly visit and follow his page on Twitter, Github, LinkedIn, or on his website.
Top comments (1)
Thank you for sharing!