Before getting into the actual code, let us first understand what functionality our NFT marketplace contract should contain. It should be able to do the following:
- Store details of a listed NFT, such as the token ID, token address, type of NFT (ERC-721 or ERC-1155), price, and the seller’s address.
- Allow users to list NFTs for sale on the marketplace (via the createListing function)
- Allow users to buy NFTs that are listed for sale (via the buyNFT function)
- Facilitate the transfer of NFTs between buyers and sellers (via the marketplace contract as an intermediary)
- Allows users to view their listed and purchased NFTs (via the public getMyListedNFTs and getMarketItem functions)
Now that we know how our NFT marketplace contract will work, let us start creating the marketplace contract.
Importing Dependencies and Declaring the Contract
Go to your contracts folder within your marketplace-hardhat folder and run the following command to create the required solidity files:
creating the nft marketplace smart contract
echo > marketplace.sol
echo > NFT.sol
echo > marketplace.sol
echo > NFT.sol
echo > marketplace.sol
echo > NFT.sol
Then, open the marketplace.sol file and input the following code:
creating the nft marketplace smart contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Marketplace is ReentrancyGuard, Ownable {
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Marketplace is ReentrancyGuard, Ownable {
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Marketplace is ReentrancyGuard, Ownable {
We will review the code in sections to understand each part fully. If you don't want to follow along, feel free to skip to the end of this section to see the complete code.
The first line of our solidity file is the license identifier. Then, in the second line, we define the version pragma we want to compile with. ^0.8.9 means we can compile the code on solidity version 0.8.9 and greater.
Next, we import all the contracts we will inherit and use. Our contract's name will be Marketplace, and it'll inherit other contracts such as ReentrancyGuard and Ownable.
Creating the State for the NFT Marketplace
Now, paste the following code under the code you previously copied into marketplace.sol:
creating the nft marketplace smart contract
using Counters for Counters.Counter;
Counters.Counter private marketplaceIds;
Counters.Counter private totalMarketplaceItemsSold;
mapping(uint => Listing) private marketplaceIdToListingItem;
struct Listing {
uint marketplaceId;
address nftAddress;
uint tokenId;
address payable seller;
address payable owner;
uint listPrice;
}
event ListingCreated(
uint indexed marketplaceId,
address indexed nftAddress,
uint indexed tokenId,
address seller,
address owner,
uint listPrice
);
using Counters for Counters.Counter;
Counters.Counter private marketplaceIds;
Counters.Counter private totalMarketplaceItemsSold;
mapping(uint => Listing) private marketplaceIdToListingItem;
struct Listing {
uint marketplaceId;
address nftAddress;
uint tokenId;
address payable seller;
address payable owner;
uint listPrice;
}
event ListingCreated(
uint indexed marketplaceId,
address indexed nftAddress,
uint indexed tokenId,
address seller,
address owner,
uint listPrice
);
using Counters for Counters.Counter;
Counters.Counter private marketplaceIds;
Counters.Counter private totalMarketplaceItemsSold;
mapping(uint => Listing) private marketplaceIdToListingItem;
struct Listing {
uint marketplaceId;
address nftAddress;
uint tokenId;
address payable seller;
address payable owner;
uint listPrice;
}
event ListingCreated(
uint indexed marketplaceId,
address indexed nftAddress,
uint indexed tokenId,
address seller,
address owner,
uint listPrice
);
In the code above, we call the using keyword on Counters.Counter to assign the Counters library to that variable. We also created two private functions, marketplaceIds and totalMarketplaceItemsSold, which will track the IDs and total NFTs sold on the marketplace.
The contract also declares a mapping, marketplaceIdToListingItem, which will map a uint to a struct called Listing. This struct will hold data such as the marketplaceId, nftAddress, tokenId, seller, owner, and listPrice.
The event ListingCreated is emitted whenever a user lists an NFT on the marketplace. This event can be useful for real-time and historical listings.
Listing NFTs to the Marketplace
Our marketplace will also need logic to list an NFT. Paste the following code to the end of your marketplace.sol file:
creating the nft marketplace smart contract
function createListing(
uint tokenId,
address nftAddress,
uint price
) public nonReentrant {
require(price > 0, "List price must be 1 wei >=");
marketplaceIds.increment();
uint marketplaceItemId = marketplaceIds.current();
marketplaceIdToListingItem[marketplaceItemId] = Listing(
marketplaceItemId,
nftAddress,
tokenId,
payable(msg.sender),
payable(address(0)),
price
);
IERC721(nftAddress).transferFrom(msg.sender, address(this), tokenId);
emit ListingCreated(
marketplaceItemId,
nftAddress,
tokenId,
msg.sender,
address(0),
price
);
}
function createListing(
uint tokenId,
address nftAddress,
uint price
) public nonReentrant {
require(price > 0, "List price must be 1 wei >=");
marketplaceIds.increment();
uint marketplaceItemId = marketplaceIds.current();
marketplaceIdToListingItem[marketplaceItemId] = Listing(
marketplaceItemId,
nftAddress,
tokenId,
payable(msg.sender),
payable(address(0)),
price
);
IERC721(nftAddress).transferFrom(msg.sender, address(this), tokenId);
emit ListingCreated(
marketplaceItemId,
nftAddress,
tokenId,
msg.sender,
address(0),
price
);
}
function createListing(
uint tokenId,
address nftAddress,
uint price
) public nonReentrant {
require(price > 0, "List price must be 1 wei >=");
marketplaceIds.increment();
uint marketplaceItemId = marketplaceIds.current();
marketplaceIdToListingItem[marketplaceItemId] = Listing(
marketplaceItemId,
nftAddress,
tokenId,
payable(msg.sender),
payable(address(0)),
price
);
IERC721(nftAddress).transferFrom(msg.sender, address(this), tokenId);
emit ListingCreated(
marketplaceItemId,
nftAddress,
tokenId,
msg.sender,
address(0),
price
);
}
Let's recap the code.
The createListing public function takes three arguments; tokenId, nftAddress and price. We use a nonReentrant modifier to prevent reentrancy, and a require statement to ensure the list price is greater than one wei (i.e., the smallest denomination of ETH). The rest of the function's logic includes incrementing the marketplaceId, adding the listing details to the Listing struct, transferring the token to the marketplace via transferFrom, and then emitting an event ListingCreated. The marketplace contract secures the NFT in the marketplace contract itself. This is different than holding the listed NFT in your wallet in that the marketplace contract doesn't have the authority to take NFTs from your wallet (i.e., the account tied to your private key) at a moment's notice.
Creating the Buy Listing Functionality
creating the nft marketplace smart contract
function buyListing(uint marketplaceItemId, address nftAddress)
public
payable
nonReentrant
{
uint price = marketplaceIdToListingItem[marketplaceItemId].listPrice;
require(
msg.value == price,
"Value sent does not meet list price for NFT"
);
uint tokenId = marketplaceIdToListingItem[marketplaceItemId].tokenId;
marketplaceIdToListingItem[marketplaceItemId].seller.transfer(msg.value);
IERC721(nftAddress).transferFrom(address(this), msg.sender, tokenId);
marketplaceIdToListingItem[marketplaceItemId].owner = payable(msg.sender);
totalMarketplaceItemsSold.increment();
}
function buyListing(uint marketplaceItemId, address nftAddress)
public
payable
nonReentrant
{
uint price = marketplaceIdToListingItem[marketplaceItemId].listPrice;
require(
msg.value == price,
"Value sent does not meet list price for NFT"
);
uint tokenId = marketplaceIdToListingItem[marketplaceItemId].tokenId;
marketplaceIdToListingItem[marketplaceItemId].seller.transfer(msg.value);
IERC721(nftAddress).transferFrom(address(this), msg.sender, tokenId);
marketplaceIdToListingItem[marketplaceItemId].owner = payable(msg.sender);
totalMarketplaceItemsSold.increment();
}
function buyListing(uint marketplaceItemId, address nftAddress)
public
payable
nonReentrant
{
uint price = marketplaceIdToListingItem[marketplaceItemId].listPrice;
require(
msg.value == price,
"Value sent does not meet list price for NFT"
);
uint tokenId = marketplaceIdToListingItem[marketplaceItemId].tokenId;
marketplaceIdToListingItem[marketplaceItemId].seller.transfer(msg.value);
IERC721(nftAddress).transferFrom(address(this), msg.sender, tokenId);
marketplaceIdToListingItem[marketplaceItemId].owner = payable(msg.sender);
totalMarketplaceItemsSold.increment();
}
Let's recap the code.
The buyListing function is a public payable function that takes in a marketplaceItemId, and nftAddress. It also utilizes the nonReentrant modifier to prevent reentrancy. The function's logic includes retrieving the list price and ensuring the value sent along with the function call meets the list price. The remainder of the logic consists of transferring the NFT to the buyer, changing the owner value in the marketplaceIdToListingItem mapping, and incrementing the totalMarketplaceItemsSold variable.
Creating Helper Functions for the NFT Marketplace
creating the nft marketplace smart contract
function getMarketItem(uint marketplaceItemId)
public
view
returns (Listing memory)
{
return marketplaceIdToListingItem[marketplaceItemId];
}
function getMyListedNFTs() public view returns (Listing[] memory) {
uint totalListingCount = marketplaceIds.current();
uint listingCount = 0;
uint index = 0;
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
listingCount += 1;
}
}
Listing[] memory items = new Listing[](listingCount);
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
uint currentId = marketplaceIdToListingItem[i + 1].marketplaceId;
Listing memory currentItem = marketplaceIdToListingItem[currentId];
items[index] = currentItem;
index += 1;
}
}
return items;
}
}
function getMarketItem(uint marketplaceItemId)
public
view
returns (Listing memory)
{
return marketplaceIdToListingItem[marketplaceItemId];
}
function getMyListedNFTs() public view returns (Listing[] memory) {
uint totalListingCount = marketplaceIds.current();
uint listingCount = 0;
uint index = 0;
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
listingCount += 1;
}
}
Listing[] memory items = new Listing[](listingCount);
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
uint currentId = marketplaceIdToListingItem[i + 1].marketplaceId;
Listing memory currentItem = marketplaceIdToListingItem[currentId];
items[index] = currentItem;
index += 1;
}
}
return items;
}
}
function getMarketItem(uint marketplaceItemId)
public
view
returns (Listing memory)
{
return marketplaceIdToListingItem[marketplaceItemId];
}
function getMyListedNFTs() public view returns (Listing[] memory) {
uint totalListingCount = marketplaceIds.current();
uint listingCount = 0;
uint index = 0;
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
listingCount += 1;
}
}
Listing[] memory items = new Listing[](listingCount);
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
uint currentId = marketplaceIdToListingItem[i + 1].marketplaceId;
Listing memory currentItem = marketplaceIdToListingItem[currentId];
items[index] = currentItem;
index += 1;
}
}
return items;
}
}
Let's recap the code.
These two functions are helper functions that will return a marketplace item and retrieve a seller's listed NFTs. The getMyListedNFTs uses a for loop to iterate and return the marketplace items.
Your complete marketplace code should look like this:
creating the nft marketplace smart contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Marketplace is ReentrancyGuard, Ownable {
using Counters for Counters.Counter;
Counters.Counter private marketplaceIds;
Counters.Counter private totalMarketplaceItemsSold;
mapping(uint => Listing) private marketplaceIdToListingItem;
struct Listing {
uint marketplaceId;
address nftAddress;
uint tokenId;
address payable seller;
address payable owner;
uint listPrice;
}
event ListingCreated(
uint indexed marketplaceId,
address indexed nftAddress,
uint indexed tokenId,
address seller,
address owner,
uint listPrice
);
function createListing(
uint tokenId,
address nftAddress,
uint price
) public nonReentrant {
require(price > 0, "List price must be 1 wei >=");
marketplaceIds.increment();
uint marketplaceItemId = marketplaceIds.current();
marketplaceIdToListingItem[marketplaceItemId] = Listing(
marketplaceItemId,
nftAddress,
tokenId,
payable(msg.sender),
payable(address(0)),
price
);
IERC721(nftAddress).transferFrom(msg.sender, address(this), tokenId);
emit ListingCreated(
marketplaceItemId,
nftAddress,
tokenId,
msg.sender,
address(0),
price
);
}
function buyListing(uint marketplaceItemId, address nftAddress)
public
payable
nonReentrant
{
uint price = marketplaceIdToListingItem[marketplaceItemId].listPrice;
require(
msg.value == price,
"Value sent does not meet list price for NFT"
);
uint tokenId = marketplaceIdToListingItem[marketplaceItemId].tokenId;
marketplaceIdToListingItem[marketplaceItemId].seller.transfer(msg.value);
IERC721(nftAddress).transferFrom(address(this), msg.sender, tokenId);
marketplaceIdToListingItem[marketplaceItemId].owner = payable(msg.sender);
totalMarketplaceItemsSold.increment();
}
function getMarketItem(uint marketplaceItemId)
public
view
returns (Listing memory)
{
return marketplaceIdToListingItem[marketplaceItemId];
}
function getMyListedNFTs() public view returns (Listing[] memory) {
uint totalListingCount = marketplaceIds.current();
uint listingCount = 0;
uint index = 0;
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
listingCount += 1;
}
}
Listing[] memory items = new Listing[](listingCount);
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
uint currentId = marketplaceIdToListingItem[i + 1].marketplaceId;
Listing memory currentItem = marketplaceIdToListingItem[currentId];
items[index] = currentItem;
index += 1;
}
}
return items;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Marketplace is ReentrancyGuard, Ownable {
using Counters for Counters.Counter;
Counters.Counter private marketplaceIds;
Counters.Counter private totalMarketplaceItemsSold;
mapping(uint => Listing) private marketplaceIdToListingItem;
struct Listing {
uint marketplaceId;
address nftAddress;
uint tokenId;
address payable seller;
address payable owner;
uint listPrice;
}
event ListingCreated(
uint indexed marketplaceId,
address indexed nftAddress,
uint indexed tokenId,
address seller,
address owner,
uint listPrice
);
function createListing(
uint tokenId,
address nftAddress,
uint price
) public nonReentrant {
require(price > 0, "List price must be 1 wei >=");
marketplaceIds.increment();
uint marketplaceItemId = marketplaceIds.current();
marketplaceIdToListingItem[marketplaceItemId] = Listing(
marketplaceItemId,
nftAddress,
tokenId,
payable(msg.sender),
payable(address(0)),
price
);
IERC721(nftAddress).transferFrom(msg.sender, address(this), tokenId);
emit ListingCreated(
marketplaceItemId,
nftAddress,
tokenId,
msg.sender,
address(0),
price
);
}
function buyListing(uint marketplaceItemId, address nftAddress)
public
payable
nonReentrant
{
uint price = marketplaceIdToListingItem[marketplaceItemId].listPrice;
require(
msg.value == price,
"Value sent does not meet list price for NFT"
);
uint tokenId = marketplaceIdToListingItem[marketplaceItemId].tokenId;
marketplaceIdToListingItem[marketplaceItemId].seller.transfer(msg.value);
IERC721(nftAddress).transferFrom(address(this), msg.sender, tokenId);
marketplaceIdToListingItem[marketplaceItemId].owner = payable(msg.sender);
totalMarketplaceItemsSold.increment();
}
function getMarketItem(uint marketplaceItemId)
public
view
returns (Listing memory)
{
return marketplaceIdToListingItem[marketplaceItemId];
}
function getMyListedNFTs() public view returns (Listing[] memory) {
uint totalListingCount = marketplaceIds.current();
uint listingCount = 0;
uint index = 0;
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
listingCount += 1;
}
}
Listing[] memory items = new Listing[](listingCount);
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
uint currentId = marketplaceIdToListingItem[i + 1].marketplaceId;
Listing memory currentItem = marketplaceIdToListingItem[currentId];
items[index] = currentItem;
index += 1;
}
}
return items;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Marketplace is ReentrancyGuard, Ownable {
using Counters for Counters.Counter;
Counters.Counter private marketplaceIds;
Counters.Counter private totalMarketplaceItemsSold;
mapping(uint => Listing) private marketplaceIdToListingItem;
struct Listing {
uint marketplaceId;
address nftAddress;
uint tokenId;
address payable seller;
address payable owner;
uint listPrice;
}
event ListingCreated(
uint indexed marketplaceId,
address indexed nftAddress,
uint indexed tokenId,
address seller,
address owner,
uint listPrice
);
function createListing(
uint tokenId,
address nftAddress,
uint price
) public nonReentrant {
require(price > 0, "List price must be 1 wei >=");
marketplaceIds.increment();
uint marketplaceItemId = marketplaceIds.current();
marketplaceIdToListingItem[marketplaceItemId] = Listing(
marketplaceItemId,
nftAddress,
tokenId,
payable(msg.sender),
payable(address(0)),
price
);
IERC721(nftAddress).transferFrom(msg.sender, address(this), tokenId);
emit ListingCreated(
marketplaceItemId,
nftAddress,
tokenId,
msg.sender,
address(0),
price
);
}
function buyListing(uint marketplaceItemId, address nftAddress)
public
payable
nonReentrant
{
uint price = marketplaceIdToListingItem[marketplaceItemId].listPrice;
require(
msg.value == price,
"Value sent does not meet list price for NFT"
);
uint tokenId = marketplaceIdToListingItem[marketplaceItemId].tokenId;
marketplaceIdToListingItem[marketplaceItemId].seller.transfer(msg.value);
IERC721(nftAddress).transferFrom(address(this), msg.sender, tokenId);
marketplaceIdToListingItem[marketplaceItemId].owner = payable(msg.sender);
totalMarketplaceItemsSold.increment();
}
function getMarketItem(uint marketplaceItemId)
public
view
returns (Listing memory)
{
return marketplaceIdToListingItem[marketplaceItemId];
}
function getMyListedNFTs() public view returns (Listing[] memory) {
uint totalListingCount = marketplaceIds.current();
uint listingCount = 0;
uint index = 0;
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
listingCount += 1;
}
}
Listing[] memory items = new Listing[](listingCount);
for (uint i = 0; i < totalListingCount; i++) {
if (marketplaceIdToListingItem[i + 1].owner == msg.sender) {
uint currentId = marketplaceIdToListingItem[i + 1].marketplaceId;
Listing memory currentItem = marketplaceIdToListingItem[currentId];
items[index] = currentItem;
index += 1;
}
}
return items;
}
}