Introduction
This technical guide will give you a comprehensive guide to building a complete NFT marketplace ecosystem using Solidity and Openzeppelin.
Our marketplace features a native token, NFT creation with customizable royalties, and secure trading mechanisms.
Technical Stack
- Solidity ^0.8.27
- OpenZeppelin Contracts
- Hardhat Development Environment
- TypeScript
- Ethers.js
Contract Structure
The project is structured into three main contracts:
- Platform Token (NYWToken.sol)
- NFT Contract (NYWNFT.sol)
- Marketplace Contract (NYWMarketplace.sol)
1. Platform Token Implementation (NYWToken.sol)
The foundation of our marketplace starts with implementing the platform's native token.
This NYWToken contract is a simple ERC20 token implementation.
It inherits from the OpenZeppelin ERC20 contract and initializes the token with a supply of 1 billion tokens.
Key Features:
- Token Supply: 1 billion tokens
- Token Standard: ERC20 compliance
- Decimals: 18 decimal places for maximum divisibility
- Initial Distribution: All tokens minted to contract deployer
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract NYWToken is ERC20 {
uint256 initialSupply = 10 ** 9;
constructor() ERC20("NYWToken", "NYW") {
_mint(msg.sender, initialSupply * 10 ** decimals());
}
}
2. NFT Contract Implementation
The NYWNFT contract handles creating and burning NYW NFT token, and royalty management.
This contract inherits from the OpenZeppelin ERC721 contract, which provides the necessary functionality for creating and managing NFTs.
Key Functions:
-
createNYW()
: Creates new NFTs with customizable royalties -
burnNYW()
: Removes NFTs from circulation -
getCreator()
: Updates metadata URI -
getRoyalty()
: Returns royalty configuration
Here is a detailed implementation of the NYWNFT contract:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract NYWNFT is ERC721URIStorage, Ownable {
uint256 private _tokenId;
mapping(uint256 => address) public creators;
mapping(uint256 => uint256) public royalties;
event NYWTokenCreated(
address indexed creator,
uint256 tokenId,
uint256 royalty,
string tokenURI
);
event NYWTokenBurned(address indexed burner, uint256 tokenId);
constructor() ERC721("NYW NFT", "NYW") Ownable(msg.sender) {}
function createNYW(
string memory tokenURI,
uint256 royalty
) external returns (uint256) {
require(
royalty >= 0 && royalty <= 30,
"Royalty must be between 0 and 30"
);
_tokenId += 1;
creators[_tokenId] = msg.sender;
royalties[_tokenId] = royalty;
_safeMint(msg.sender, _tokenId);
_setTokenURI(_tokenId, tokenURI);
_setApprovalForAll(msg.sender, address(this), true);
emit NYWTokenCreated(msg.sender, _tokenId, royalty, tokenURI);
return _tokenId;
}
function burnNYW(uint256 tokenId) external {
require(
msg.sender == ownerOf(tokenId),
"Only the owner can burn the token"
);
_burn(tokenId);
delete creators[tokenId];
delete royalties[tokenId];
emit NYWTokenBurned(msg.sender, tokenId);
}
function getCreator(uint256 tokenId) external view returns (address) {
return creators[tokenId];
}
function getRoyalty(uint256 tokenId) external view returns (uint256) {
return royalties[tokenId];
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
3. Marketplace Contract Implementation (NYWMarketplace.sol)
The NYWMarketplace contract manages listings, delisting and buying NFTs and withdrawing funds:
Key Functions:
-
listNFT(uint256 tokenId_, uint256 price_)
: Lists new NFT to marketplace for sale -
delistNFT(uint256 tokenId_)
: Removes NFT from marketplace -
buyNFT(uint256 tokenId_)
: Purchases NFT from marketplace -
withdraw()
: Withdraws funds from marketplace
function listNFT(uint256 tokenId_, uint256 price_) external {
// Check if the NFT is already listed
require(!isExisting[tokenId_], "NFT is already listed");
// Check if the NFT is owned by the caller
require(
nftContract.ownerOf(tokenId_) == msg.sender,
"You are not the owner of the NFT"
);
//Define new NFTMarketItem
NFTMarketItem memory newNFTMarketItem = NFTMarketItem(
tokenId_,
price_,
nywContract.getRoyalty(tokenId_),
payable(msg.sender),
nywContract.getCreator(tokenId_),
true
);
isExisting[tokenId_] = true;
listedNFTs.push(newNFTMarketItem);
tokenIdToNFTMarketItemId[tokenId_] = totalListedNFTs;
totalListedNFTs++;
emit NYWNFTListed(
tokenId_,
price_,
nywContract.getRoyalty(tokenId_),
nywContract.getCreator(tokenId_),
msg.sender
);
}
function delistNFT(uint256 tokenId_) external {
require(isExisting[tokenId_], "NFT is not listed");
uint256 nftMarketItemId = tokenIdToNFTMarketItemId[tokenId_];
require(
listedNFTs[nftMarketItemId].seller == msg.sender,
"Only the seller can delist the NFT"
);
deleteNFTfromArray(tokenId_);
emit NYWNFTDelisted(tokenId_);
}
function deleteNFTfromArray(uint256 tokenId_) public {
uint256 nftMarketItemId = tokenIdToNFTMarketItemId[tokenId_];
uint256 lastIndex = listedNFTs.length - 1;
listedNFTs[nftMarketItemId] = listedNFTs[lastIndex];
tokenIdToNFTMarketItemId[
listedNFTs[lastIndex].tokenId
] = nftMarketItemId;
listedNFTs.pop();
totalListedNFTs--;
delete tokenIdToNFTMarketItemId[tokenId_];
isExisting[tokenId_] = false;
}
function buyNFT(uint256 tokenId_) public payable {
require(isExisting[tokenId_], "NFT is not listed");
uint256 _nftMarketItemId = tokenIdToNFTMarketItemId[tokenId_];
NFTMarketItem memory _nftMarketItem = listedNFTs[_nftMarketItemId];
require(!_nftMarketItem.isSold, "NFT is already sold");
require(msg.value == _nftMarketItem.price, "Incorrect payment amount");
// Calculate royalty amount
uint256 _royaltyAmount = (_nftMarketItem.price *
_nftMarketItem.royalty) / deno;
// Calculate platform fee
uint256 _platformFeeAmount = (_nftMarketItem.price * platformFee) /
deno;
// Calculate seller amount
uint256 _sellerAmount = _nftMarketItem.price -
_royaltyAmount -
_platformFeeAmount;
// Transfer funds to the seller
payable(_nftMarketItem.seller).transfer(_sellerAmount);
// Transfer royalty amount to the creator
payable(_nftMarketItem.creator).transfer(_royaltyAmount);
// Transfer platform fee amount to the platform
payable(owner()).transfer(_platformFeeAmount);
//Transfer NFT to the buyer
nftContract.safeTransferFrom(
_nftMarketItem.seller,
msg.sender,
_nftMarketItem.tokenId
);
_nftMarketItem.isSold = true;
deleteNFTfromArray(tokenId_);
emit NYWNFTBought(
_nftMarketItem.tokenId,
msg.sender,
_nftMarketItem.seller,
_nftMarketItem.price
);
}
function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No funds to withdraw");
payable(owner()).transfer(balance);
emit NYWNFTWithdrawn(balance);
}
Conclusion
Building a secure and efficient NFT marketplace requires careful implementation of multiple smart contracts working together. This guide covered the essential components: platform token, NFT contract, and marketplace functionality.
Top comments (20)
Greate post
Thanks for your effort. 👍👍👍
Thanks for your article
Thanks for your effort.
I can get the general understanding how to write NFT smart contract
Thanks for your article.
This article is incredibly insightful and packed with valuable information in writing NFT marketplace smart contract step by step.
Thanks
Great article.
Thanks
Great
Good article.
Thank you
Thanks for sharing great article
Thank you.
Good article