Skip to content

Instantly share code, notes, and snippets.

@dabit3
Last active March 14, 2024 15:55
Show Gist options
  • Star 77 You must be signed in to star a gist
  • Fork 24 You must be signed in to fork a gist
  • Save dabit3/eb8866adc22bd86cabf5e6604b408e4a to your computer and use it in GitHub Desktop.
Save dabit3/eb8866adc22bd86cabf5e6604b408e4a to your computer and use it in GitHub Desktop.
NFT Marketplace Smart Contract (V2)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "hardhat/console.sol";
contract NFTMarketplace is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
Counters.Counter private _itemsSold;
uint256 listingPrice = 0.025 ether;
address payable owner;
mapping(uint256 => MarketItem) private idToMarketItem;
struct MarketItem {
uint256 tokenId;
address payable seller;
address payable owner;
uint256 price;
bool sold;
}
event MarketItemCreated (
uint256 indexed tokenId,
address seller,
address owner,
uint256 price,
bool sold
);
constructor() ERC721("Metaverse Tokens", "METT") {
owner = payable(msg.sender);
}
/* Updates the listing price of the contract */
function updateListingPrice(uint _listingPrice) public payable {
require(owner == msg.sender, "Only marketplace owner can update listing price.");
listingPrice = _listingPrice;
}
/* Returns the listing price of the contract */
function getListingPrice() public view returns (uint256) {
return listingPrice;
}
/* Mints a token and lists it in the marketplace */
function createToken(string memory tokenURI, uint256 price) public payable returns (uint) {
_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
_mint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI);
createMarketItem(newTokenId, price);
return newTokenId;
}
function createMarketItem(
uint256 tokenId,
uint256 price
) private {
require(price > 0, "Price must be at least 1 wei");
require(msg.value == listingPrice, "Price must be equal to listing price");
idToMarketItem[tokenId] = MarketItem(
tokenId,
payable(msg.sender),
payable(address(this)),
price,
false
);
_transfer(msg.sender, address(this), tokenId);
emit MarketItemCreated(
tokenId,
msg.sender,
address(this),
price,
false
);
}
/* allows someone to resell a token they have purchased */
function resellToken(uint256 tokenId, uint256 price) public payable {
require(idToMarketItem[tokenId].owner == msg.sender, "Only item owner can perform this operation");
require(msg.value == listingPrice, "Price must be equal to listing price");
idToMarketItem[tokenId].sold = false;
idToMarketItem[tokenId].price = price;
idToMarketItem[tokenId].seller = payable(msg.sender);
idToMarketItem[tokenId].owner = payable(address(this));
_itemsSold.decrement();
_transfer(msg.sender, address(this), tokenId);
}
/* Creates the sale of a marketplace item */
/* Transfers ownership of the item, as well as funds between parties */
function createMarketSale(
uint256 tokenId
) public payable {
uint price = idToMarketItem[tokenId].price;
address seller = idToMarketItem[tokenId].seller;
require(msg.value == price, "Please submit the asking price in order to complete the purchase");
idToMarketItem[tokenId].owner = payable(msg.sender);
idToMarketItem[tokenId].sold = true;
idToMarketItem[tokenId].seller = payable(address(0));
_itemsSold.increment();
_transfer(address(this), msg.sender, tokenId);
payable(owner).transfer(listingPrice);
payable(seller).transfer(msg.value);
}
/* Returns all unsold market items */
function fetchMarketItems() public view returns (MarketItem[] memory) {
uint itemCount = _tokenIds.current();
uint unsoldItemCount = _tokenIds.current() - _itemsSold.current();
uint currentIndex = 0;
MarketItem[] memory items = new MarketItem[](unsoldItemCount);
for (uint i = 0; i < itemCount; i++) {
if (idToMarketItem[i + 1].owner == address(this)) {
uint currentId = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
/* Returns only items that a user has purchased */
function fetchMyNFTs() public view returns (MarketItem[] memory) {
uint totalItemCount = _tokenIds.current();
uint itemCount = 0;
uint currentIndex = 0;
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].owner == msg.sender) {
itemCount += 1;
}
}
MarketItem[] memory items = new MarketItem[](itemCount);
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].owner == msg.sender) {
uint currentId = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
/* Returns only items a user has listed */
function fetchItemsListed() public view returns (MarketItem[] memory) {
uint totalItemCount = _tokenIds.current();
uint itemCount = 0;
uint currentIndex = 0;
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].seller == msg.sender) {
itemCount += 1;
}
}
MarketItem[] memory items = new MarketItem[](itemCount);
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].seller == msg.sender) {
uint currentId = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
}
@jaglinux
Copy link

jaglinux commented Mar 3, 2022

@d2vin
Copy link

d2vin commented Mar 5, 2022

https://gist.github.com/dabit3/eb8866adc22bd86cabf5e6604b408e4a#file-marketplace-sol-L113 , seller address is 0 ? (refer L109)

@jaglinux This confused me initially too, the createMarketSale function transfers the token from the marketplace, to the buyer. Prior to this function, the owner was the marketplace contract, and the seller was the person who listed the token on the marketplace. The marketplace then transfers ownership of the token from itself, to the buyer, and pays the listingPrice to the seller. Line 109 then changes the marketplace metadata of the token within the idToMarketItem struct, and since the token is sold, and unlisted (no one is currently selling the token), the seller value is 0x0, which is equal to a null address. Hope this helps! And shoutout @dabit3 for updating an all-time favorite, hoping to utilize this knowledge to allow the original minters of token to also earn a listing fee for the resell of their tokens!

@jaglinux
Copy link

jaglinux commented Mar 5, 2022

@d2vin The code was updated yesterday.
This is the earlier code which had the bug.
function createMarketSale(
uint256 tokenId
) public payable {
uint price = idToMarketItem[tokenId].price;
require(msg.value == price, "Please submit the asking price in order to complete the purchase");
idToMarketItem[tokenId].owner = payable(msg.sender);
idToMarketItem[tokenId].sold = true;
idToMarketItem[tokenId].seller = payable(address(0));
_itemsSold.increment();
_transfer(address(this), msg.sender, tokenId);
payable(owner).transfer(listingPrice);
payable(idToMarketItem[tokenId].seller).transfer(msg.value);
}

hmm...expected author to acknowledge .

@dabit3
Copy link
Author

dabit3 commented Mar 5, 2022

Hey @jaglinux yes this was brought up to me here but also in my tutorial comments, I appreciate you flagging, hope this fixes the issue!

@jaglinux
Copy link

jaglinux commented Mar 5, 2022

@dabit3 yes it works , thanks

@chajaykrishna
Copy link

chajaykrishna commented Mar 9, 2022

do we really have to do this?

MarketItem storage currentItem = idToMarketItem[currentId];
 items[currentIndex] = currentItem;

isn't this better? less gas
items[currentIndex] =idToMarketItem[currentId];

can someone pls explain, I'm a newbie.
thanks :)

@d2vin
Copy link

d2vin commented Mar 10, 2022

@chajaykrishna the reasoning for this lies within the need to declare the storage keyword. Since we are iterating through a for loop and will need to access multiple instances of the currentItem, in order to add each currentItem into the items array need to place, and then access them via the storage area of the smart contract. More on the storage keyword here.

@itzomen
Copy link

itzomen commented Apr 23, 2022

Any idea or resource for creating multiple collections? I am currently thinking of using Clones.clone(MarketAddress) to create several collections and saving references in the calling smart contract.

NB: I'm a Newbie

@kofkuiper
Copy link

kofkuiper commented Jun 3, 2022

Hi @itzomen I'm trying to create NFT Marketplace that support creating new collection and more features. Hope this helps. https://github.com/kofkuiper/kuiper-nft-marketplace

@itzomen
Copy link

itzomen commented Jun 3, 2022

Hi @itzomen I'm trying to create NFT Marketplace that support creating new collection and more features. Hope this helps. https://github.com/kofkuiper/kuiper-nft-marketplace

Thanks taking a look

@90khan26
Copy link

Can I use this market place contract for my contract written with the ERC1155 standard, or should I rewrite the marketplace contract with ERC1155 from the beginning?

@AbuSantos
Copy link

Hi @itzomen I'm trying to create NFT Marketplace that support creating new collection and more features. Hope this helps. https://github.com/kofkuiper/kuiper-nft-marketplace

Hy Kofkuiper, this repo is amazing, wondering if could ask you some questions

@kofkuiper
Copy link

Hey @AbuSantos, Sure. What is it?

@derawl
Copy link

derawl commented Oct 6, 2022

Is it better to abstract the market place from the NFT contract so NFTs from different collections can be sold?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment