Github Repository purple-ice-lite
In this step-by-step guide, we’ll show you how to build a decentralized NFT marketplace app, similar to OpenSea, with features such as Connect Wallet, Mint, Sale, Buy, and Display. By the end of this tutorial, you’ll have a deeper understanding of the NFT trading process, and an introduction to writing smart contracts and using Web3 APIs. You can also apply these skills to develop your own DApp (such as creating a Mint page for NFTs you issue yourself).
To ensure a good reading experience, we recommend using OpenSea or other mainstream NFT marketplaces and knowing or understanding the following prerequisite knowledge: blockchain, Bitcoin, Ethereum and Ether, MetaMask, NFT, HTML, CSS, JS.
The following content is divided into five layers: blockchain -> smart contracts -> Web3 API -> DApp -> users and authentication. Each layer will explain the reasons and relationship with the previous layer, and will be organized in a progressive way, solving each problem one by one.
Now, let’s get started.
The NFT marketplace we are building is based on blockchain. In other words, the data generated by users’ core operations will be recorded on the blockchain and cannot be tampered with. It should be noted that only core operations will be recorded, as users’ avatars, nicknames, etc. will not affect the ownership of NFTs and other information related to sales can be recorded in a traditional database. This makes development more convenient and reduces the amount of information recorded in the blockchain, thereby lowering operational costs. This article will focus on core operations and does not involve third-party storage.
During the development process, if we need to operate on the Ethereum main chain, we will need to pay considerable gas fees every time we test. If the smart contract fails to compile, it will also waste blockchain resources. For the purposes of learning, we can set up a blockchain for testing, which does not incur any real costs and does not waste any blockchain resources, making testing and debugging easier.
There are two ways to use a test blockchain. One is to use an online test blockchain (Testnet), such as Mumbai Polygon. Its usage is almost identical to that of the real Ethereum, except that you can receive some ETH for free via certain websites, which makes testing very convenient.
The second way is to set up a local blockchain and use tools such as Ganache or Hardhat. Both tools will provide some default accounts with sufficient balance for testing when the blockchain starts running.
Here, we choose the second way to set up a local blockchain. We use the Hardhat scaffolding tool which allows us to run a local blockchain, compile contracts, and deploy contracts directly inside the scaffolding project. The usage is simple enough for our purposes.
This project is named purple-ice-lite
, and all subsequent operations will be performed in this directory.
mkdir purple-ice-lite && cd ./purple-ice-lite
First, create a folder named purple-ice-lite/chain
to manage the blockchain and smart contracts.
mkdir chain && cd ./chain
Initialize with yarn and hardhat:
yarn init -y
yarn add -D hardhat
yarn hardhat
After running yarn hardhat
, you will be prompted to select an initialization guide. Select the “advanced + ts” option:
? What do you want to do? ...
Create a basic sample project
Create an advanced sample project
> Create an advanced sample project that uses TypeScript
Create an empty hardhat.config.js
Quit
The guide will then prompt you to add a .gitignore
file:
√ Do you want to add a .gitignore? (Y/n) · y
You can then move the .gitignore
file to the purple-ice-lite
directory for better project management.
Once the initialization process is complete, copy and run the required dependency installation command in the command line. For example, if you see the following prompt:
yarn add --dev "hardhat@^2.9.6" "@nomiclabs/hardhat-waffle@^2.0.0" "ethereum-waffle@^3.0.0" "chai@^4.2.0" "@nomiclabs/hardhat-ethers@^2.0.0" "ethers@^5.0.0" "@nomiclabs/hardhat-etherscan@^3.0.0" "dotenv@^16.0.0" "eslint@^7.29.0" "eslint-config-prettier@^8.3.0" "eslint-config-standard@^16.0.3" "eslint-plugin-import@^2.23.4" "eslint-plugin-node@^11.1.0" "eslint-plugin-prettier@^3.4.0" "eslint-plugin-promise@^5.1.0" "hardhat-gas-reporter@^1.0.4" "prettier@^2.3.2" "prettier-plugin-solidity@^1.0.0-beta.13" "solhint@^3.3.6" "solidity-coverage@^0.7.16" "@typechain/ethers-v5@^7.0.1" "@typechain/hardhat@^2.3.0" "@typescript-eslint/eslint-plugin@^4.29.1" "@typescript-eslint/parser@^4.29.1" "@types/chai@^4.2.21" "@types/node@^12.0.0" "@types/mocha@^9.0.0" "ts-node@^10.1.0" "typechain@^5.1.2" "typescript@^4.5.2"
To run a local blockchain, use the command:
yarn hardhat node
This will output some default accounts, indicating the blockchain is running successfully. You can use MetaMask to visualize the state of the accounts on the blockchain.
Now that we have a running blockchain, we need to store and modify data on the blockchain in a decentralized manner. Bitcoin’s blockchain is not well-suited for this, but Ethereum’s blockchain has a different data storage model and introduces the concept of “smart contracts” to support decentralized data storage.
Smart contracts are blocks of code and data that are stored and executed on the Ethereum blockchain using the Solidity programming language. The functions in this code can be called externally to perform logic tests, store data, transfer money, and interact with other smart contracts. This ensures that anyone can access this code and perform operations on the blockchain’s data by executing fixed and tamper-proof logic.
As an NFT marketplace, we need goods to trade. In this section, we will create an NFT project and store and publish its data on the blockchain we just built.
To create an NFT project, we need to write and deploy a smart contract that automatically identifies NFT ownership and transactions on the blockchain. Although we can write a smart contract from scratch, it is more recommended to inherit existing token standards to reduce the workload and ensure better recognition by other platforms.
One of the NFT token standards is ERC-721. It is a pre-written smart contract code that can be directly inherited by our NFT smart contract to obtain some common code that has been written. ERC-721 also has some extension types, such as ERC721Enumerable, which adds some enumeration functions to easily obtain NFT data.
Here, ERC721Enumerable is used for demonstrating NFT data for convenience. However, enumerating each time incurs a large amount of gas fees, so it should not be used in formal projects. Formal projects can choose a more cost-effective token standard.
OpenZeppelin Contracts is a smart contract code library that includes the most commonly used ERC standards. We will import the required code from it this time.
First, we need to install the @openzeppelin/contracts
Solidity library in our project.
yarn add @openzeppelin/contracts
Rename the sample smart contract in the project from purple-ice-lite/chain/contracts/Greeter.sol
to purple-ice-lite/chain/contracts/BadgeToken.sol
.
The smart contract code inherits from ERC721Enumerable and adds three functions:
mintTo
function can be called to mint a new NFT.tokenURI()
function returns the basic information of the NFT, such as the name, description, and image. Here, we use base64 encoding to include an SVG image in the basic information of the NRename the example smart contract in the project from purple-ice-lite/chain/contracts/Greeter.sol
to purple-ice-lite/chain/contracts/BadgeToken.sol
.The smart contract code inherits from ERC721Enumerable and adds three functions:
mintTo
function can be called to mint a new NFT.tokenURI()
function is used to return basic information about the NFT, such as name, description, and image. Here, the image uses a base64-encoded SVG imageRename the sample smart contract name in the project and modify purple-ice-lite/chain/contracts/Greeter.sol
to purple-ice-lite/chain/contracts/BadgeToken.sol
.The smart contract code inherits from ERC721Enumerable and adds three functions:
mintTo
function can be called to mint a new NFT.tokenURI()
returns the basic information of the NFT, such as name, description, image, etc. Here, a svg image is encoded through base64 and included in the NFT basic information.All code is in BadgeToken.sol, which refers to the implementation in this article. You can go to the original article link to see the writing details.
Compile the smart contract code:
yarn hardhat compile
Next, we will write unit tests to ensure that the contract works as expected.
Create BadgeToken.test.ts
in the purple-ice-lite/chain/test
folder, and the content is linked here BadgeToken.test.ts.
The code uses the base-64
library, install it and run the test:
yarn add -D base-64
yarn hardhat test test\BadgeToken.test.ts
You will see the test results:
BadgeToken
✔ Should has the correct name and symbol (41ms)
✔ Should tokenId start from 1 and auto increment (100ms)
✔ Should mint a token with event
✔ Should mint a token with desired tokenURI (log result for inspection) (137ms)
✔ Should mint 10 token with desired tokenURI (893ms)
After writing the smart contract and it can run as expected, we can deploy it on the blockchain.
Write a deployment script and add the deployment file deploy_BadgeToken.ts
in purple-ice-lite/chain/scripts
. Its content is in deploy_BadgeToken.ts
Run the deployment script:
yarn hardhat run .\scripts\deploy_BadgeToken.ts --network localhost
Note that this requires the local blockchain to be in a running state, which means that yarn hardhat node
has been executed. After successful deployment, the contract address is returned, and the NFT is published on the blockchain.
After creating NFTs on the blockchain, we can now trade them using a smart contract that controls the trading process. When a transaction occurs, the transaction action triggers the smart contract code on the blockchain, and the smart contract automatically completes the transaction.
Create a new contract file NFTMarketplace.sol
in the purple-ice-lite/chain/contracts
directory.
Define the structure of each market item in the marketplace as follows:
struct MarketItem {
uint id;
address nftContract;
uint256 tokenId;
address payable seller;
address payable buyer;
uint256 price;
State state;
}
It should be noted that the ID of each item is not equal to the tokenId of each NFT. When an item is created, it is indexed by tokenId, while when purchased, it is indexed by the item’s id.
Each item has three states:
enum State { Created, Release, Inactive }
The process of trading items is the process of creating items and modifying item state. To implement these operations, we have created three core functions to change the data of items in the marketplace:
function createMarketItem(address nftContract, uint256 tokenId, uint256 price) payable
function deleteMarketItem(uint256 itemId) public
function createMarketSale(address nftContract, uint256 id) public payable
At the same time, we have also created three query functions to facilitate the querying of information about items in the NFT marketplace:
function fetchActiveItems() public view returns (MarketItem[] memory)
function fetchMyPurchasedItems() public view returns (MarketItem[] memory)
function fetchMyCreatedItems() public view returns (MarketItem[] memory)
In this contract, it is stipulated that if you want to list an item, you need to pay a listing fee to the NFT marketplace creator (us, the default Account #0), which is 0.025 ether.
uint256 public listingFee = 0.025 ether;
function getListingFee() publicNow that we have NFTs on the blockchain, we can start NFT trading. Here, we use a smart contract to control the trading process. When a trade occurs, the trading action triggers the smart contract code on the blockchain, and the smart contract automatically completes the trade.
Create a new contract file `NFTMarketplace.sol` in the `purple-ice-lite/chain/contracts` directory.
Define the structure of each item in the market:
```solidity
struct MarketItem {
uint id;
address nftContract;
uint256 tokenId;
address payable seller;
address payable buyer;
uint256 price;
State state;
}
It should be noted that the id
of each item is not equal to the tokenId
of each NFT. When creating the item, it is indexed by tokenId
, and when purchasing, it is indexed by the item id
.
Each item has three states:
enum State { Created, Release, Inactive }
The process of trading items is the process of creating items and modifying their status. To implement these operations, we create three core functions to change the data of items in the market:
function createMarketItem(address nftContract,uint256 tokenId,uint256 price) payable
function deleteMarketItem(uint256 itemId) public
function createMarketSale(address nftContract,uint256 id) public payable
We also created three query functions to facilitate querying of item information in the NFT marketplace:
function fetchActiveItems() public view returns (MarketItem[] memory)
function fetchMyPurchasedItems() public view returns (MarketItem[] memory)
function fetchMyCreatedItems() public view returns (MarketItem[] memory)
In this contract, we stipulate that in order to list an item, the lister (us, the default Account #0) must pay a listing fee of 0.025 ether to the NFT marketplace creator.
uint256 public listingFee = 0.025 ether;
function getListingFee() public view returns (uint256)
The seller’s action path is:
The buyer’s action path is:
The entire code is in NFTMarketplace.sol, and the above content is referenced from this article.
Similarly, we also need to write unit tests for the NFT marketplace to ensure that it works as expected. Add the unit test code NFTMarketplace.test.ts in the purple-ice-lite/chain/test
and run the unit test:
yarn hardhat test .\test\NFTMarketplace.test.ts
After successful testing, it can be deployed to the blockchain:
yarn hardhat run .\scripts\deploy_Marketplace.ts --network localhost
After successful deployment, the deployment address will be returned.
Now that our NFT project and NFT marketplace are up and running on the blockchain, the challenge is how to access and use them. The code is compiled into binary form stored on the blockchain, and we cannot directly call it using other programming languages.
Fortunately, there are many libraries available to make calling smart contracts easier. These libraries parse smart contracts on the blockchain and simplify interactions between business layer code and the blockchain, allowing us to use them without needing detailed knowledge of blockchain operations.
For a Python project, we can use web3.py to interact with the blockchain and its smart contracts using the Python programming language. However, for building a DApp web page to implement the NFT marketplace, we need to choose a Web3 API library suitable for JavaScript. The two most commonly used libraries are Web3.js and ether.js, both of which can achieve the desired functionality. However, since the author is more familiar with the API of ethers.js, it will be used as the bridge between the front-end and the blockchain.
Now we can start writing our business layer code, which comprises the HTML web page that users will use to interact with our platform. We only need to perform the corresponding logic according to the user’s actions in the JavaScript code and call the smart contract’s interfaces. The smart contract then manipulates the information on the blockchain, and we have completed the process of reading and writing content on the blockchain based on user actions.
Here, we will create a simple NFT marketplace demo using vanilla JavaScript, which can be easily ported to any framework. You can watch the demo video here.
All the code for the demo can be found here, and the directory structure is as follows:
./webapp
├── contractsABIs
├── base64.js
├── index.html
└── logic.js
index.html
is the webpage for the NFT marketplace, with the main logic in logic.js
. base64.js
is used for decoding NFT data, and the content of contractsABIs
is used to connect to smart contracts.
The contractsABIs
folder contains the ABI of two smart contracts, which define the interfaces, such as callable methods and parameters, and are generated by the smart contract compiler. These can be found in the corresponding JSON files for each contract in purple-ice-lite/chain/artifacts/contracts/
. Here, we manually create a JS file for each contract for easier referencing.
In logic.js
, the first line sets the deployment address of the NFT project smart contract, and the second line sets the deployment address of the NFT marketplace smart contract.
The different functionalities in logic.js
correspond to different functions:
async function connectWallet(); // Connect the wallet
async function FreshMyNFT(); // Refresh my NFT balance
async function mint(); // Mint a new NFT
async function FreshMarketNFT();// Refresh NFT marketplace products
async function sale(tokenId); // Sell the current tokenId NFT
async function buy(id); // Purchase the current ID NFT
These functions are straightforward and consist of two steps:
In Step 1, we need to address the issue of authentication, which involves verifying that a particular user has performed an operation themselves. This is resolved as follows: when connecting the wallet, we obtain a signer object, which is required as a parameter for the smart contract interfaces that require authentication. This produces a pop-up that prompts the user to sign off using the MetaMask browser extension. Once signed, the transaction (operation) is executed.
The ability to manipulate assets remains with the cryptocurrency wallet, and manipulating the wallet is a user action.
To summarise, when a user interacts with the front-end DApp page, the UI logic calls methods from the Web3 API, which can be used to pop-up a plugin in the browser to verify the user’s identity. After the verification is successful, the Web3 API method calls the smart contract interface, which modifies or queries the data on the blockchain.
After a successful operation, you will need to manually refresh the NFT status to view the changes.
Based on the above content, after configuring the network and preparing some fees using tools such as Hardhat or Truffle, we can deploy smart contracts to a real blockchain.
In this article, we have a rough understanding of how NFTs and NFT marketplaces work and have tried to get started with Web3 programming. With these preparations, it should be no problem to write a Mint page for your own NFT project.
This article concludes here. Due to space limitations, many topics were not deeply covered. For more information, please refer to the links below. If there are any errors, please point them out.
I hopeSummary
Based on the above content, after configuring the network and preparing some fees using tools such as Hardhat or Truffle, you can publish the smart contract to a real blockchain.
In this article, we have a general understanding of how NFT and NFT marketplace work, and we have tried to get started with Web3 programming. With these foundations, you should have no problem writing a Mint page for your own NFT project.
The article ends here, and due to space constraints, many topics were not explored in depth. For more information, please refer to the links below. If there are any errors or mistakes, please feel free to point them out.
I hope this article can help you better understand Web3 and NFT through this method. Thank you for reading attentively.
Always ensure the safety of your funds, and have fun playing~
(完)
CC BY-NC-SA 4.0 2015-PRESENT © qer Powered by Vite, Vercel. Made with ❤️