Build NFT trading market on Ethereum

Introduction to NFT

The full name of NFT is non Fungible Token. Non homogenous token is the only cryptocurrency token used to represent digital assets. It is indivisible, irreplaceable and unique. On the blockchain, digital cryptocurrencies are divided into two categories: native currency and token. Native currency, such as bitcoin and Ethernet, has its own main chain and uses transactions on the chain to maintain ledger data. Tokens are attached to the existing blockchain and use smart contracts to record the ledger, such as tokens issued on Ethereum. Tokens can be divided into homogenization and non homogenization. Homogeneous tokens, namely FT (Fungible Token), can replace each other and be close to infinitely split tokens. For example, there is essentially no difference between a bitcoin in your hand and a bitcoin in mine. This is homogenization, that is, homogenization coin [1].

① Application of NFT non homogenous token: due to its non homogenous and inseparable characteristics, NFT non homogenous token can be bound to some commodities in the real world. In other words, it is actually a digital asset issued on the blockchain. This asset can be game props, digital artworks, tickets, etc., and is unique and non reproducible. Because NFT has natural collection properties and is easy to trade, encryption artists can use NFT to create unique digital works of art [2].
② Characteristics of NFT non homogenous token: indivisible: NFT non homogenous token is unique and cannot be divided into smaller units. Any bitcoin or Ethereum can be replaced by another bitcoin or Ethereum, and they can be divided into smaller units, such as 0.1btc or 0.01btc. Unique: NFT non homogenous tokens are non interchangeable assets with unique attributes. This means that if NFT assets themselves are scarce, they will also carry their value and cannot be tampered with or copied. Transparency: buyers can see the history of each past seller and bidder, what price they paid, and the current market situation of any digital goods.

Before that, there were NFTs built using Chainlink VRF and verifiable RNG, which created dynamic NFTs that changed according to real-world data. By using decentralized Oracle to obtain data, random NFT is added to OpenSea [2].

Construction of digital trading market

introduction
The first thing to do to establish a digital trading market is to write smart contracts.
The market will consist of two main smart contracts:
① NFT contract for casting NFT
② Market contracts to promote NFT sales
To write NFT, we can use the ERC721 standard available through OpenZeppelin.
First, go to https://remix.ethereum.org/ And create one called marketplace New file for sol.
You can start by importing the contract required to start with Open Zeppelin:

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.3;

import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import "hardhat/console.sol";

Create NFT contract

contract NFT is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    address contractAddress;

    constructor(address marketplaceAddress) ERC721("Eat The Blocks NFTs", "ETBNFT") {
        contractAddress = marketplaceAddress;
    }

    function createToken(string memory tokenURI) public returns (uint) {
        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();

        _mint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);
        setApprovalForAll(contractAddress, true);
        return newItemId;
    }
}

The constructor accepts the parameters of the marketplaceAddress address, saves the value and makes it available in the smart contract. In this way, when someone wants to create a contract, the contract can allow the market contract to approve the transfer of tokens from the owner to the seller. The newItemId value is returned from the function because we will need it in our client application to understand the dynamic value generated by the tokenId smart contract.

Create market contract
Market contracts are more complex than NFT contracts, but they are also an important part of the project.

contract NFTMarket is ReentrancyGuard {
  using Counters for Counters.Counter;
  Counters.Counter private _itemIds;
  Counters.Counter private _itemsSold;

  struct MarketItem {
    uint itemId;
    address nftContract;
    uint256 tokenId;
    address payable seller;
    address payable owner;
    uint256 price;
  }

  mapping(uint256 => MarketItem) private idToMarketItem;

  event MarketItemCreated (
    uint indexed itemId,
    address indexed nftContract,
    uint256 indexed tokenId,
    address seller,
    address owner,
    uint256 price
  );

  function getMarketItem(uint256 marketItemId) public view returns (MarketItem memory) {
    return idToMarketItem[marketItemId];
  }

  function createMarketItem(
    address nftContract,
    uint256 tokenId,
    uint256 price
  ) public payable nonReentrant {
    require(price > 0, "Price must be at least 1 wei");

    _itemIds.increment();
    uint256 itemId = _itemIds.current();
  
    idToMarketItem[itemId] =  MarketItem(
      itemId,
      nftContract,
      tokenId,
      payable(msg.sender),
      payable(address(0)),
      price
    );

    IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);

    emit MarketItemCreated(
      itemId,
      nftContract,
      tokenId,
      msg.sender,
      address(0),
      price
    );
  }

  function createMarketSale(
    address nftContract,
    uint256 itemId
    ) public payable nonReentrant {
    uint price = idToMarketItem[itemId].price;
    uint tokenId = idToMarketItem[itemId].tokenId;
    require(msg.value == price, "Please submit the asking price in order to complete the purchase");

    idToMarketItem[itemId].seller.transfer(msg.value);
    IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
    idToMarketItem[itemId].owner = payable(msg.sender);
    _itemsSold.increment();
  }

  function fetchMarketItem(uint itemId) public view returns (MarketItem memory) {
    MarketItem memory item = idToMarketItem[itemId];
    return item;
  }

  function fetchMarketItems() public view returns (MarketItem[] memory) {
    uint itemCount = _itemIds.current();
    uint unsoldItemCount = _itemIds.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(0)) {
        uint currentId = idToMarketItem[i + 1].itemId;
        MarketItem storage currentItem = idToMarketItem[currentId];
        items[currentIndex] = currentItem;
        currentIndex += 1;
      }
    }
   
    return items;
  }

  function fetchMyNFTs() public view returns (MarketItem[] memory) {
    uint totalItemCount = _itemIds.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 = idToMarketItem[i + 1].itemId;
        MarketItem storage currentItem = idToMarketItem[currentId];
        items[currentIndex] = currentItem;
        currentIndex += 1;
      }
    }
   
    return items;
  }
}

Inherits non reentrant from reentrencyguard, which can be applied to functions to ensure that there are no nested (reentrant) calls to them. We store records of items we want to provide in the market in the data structure of MarketItem. idToMarketltem is a key value mapping that allows us to create between IDs and MarketIItem. The function of createMarketItem is to transfer the NFT to the trading address in the market so that the NFT can be sold. createMarketSale is used as a medium between NFT and Eth so that they can be converted in transactions. fetchMarketItems can help you view all NFTs sold in the market. fetchMyNFTs can view your purchase records.

Build front end
The existing initial project is used in the construction of the front end.
marketplace_starter

Build user interface
Building a user interface is a key project in this digital market, including the following functions: acquiring NFT, creating NFT and selling it, allowing users to buy NFT and allowing users to view their own NFT.
The loadNFTs function returns the unsold NFT in the market by calling fetchMarkItems.

async function loadNFTs() {
  const provider = new ethers.providers.JsonRpcProvider()
  const tokenContract = new ethers.Contract(nftaddress, NFT.abi, provider)
  const marketContract = new ethers.Contract(nftmarketaddress, Market.abi, provider)
  const data = await marketContract.fetchMarketItems()
  
  const items = await Promise.all(data.map(async i => {
    const tokenUri = await tokenContract.tokenURI(i.tokenId)
    const meta = await axios.get(tokenUri)
    let price = web3.utils.fromWei(i.price.toString(), 'ether');
    let item = {
      price,
      tokenId: i.tokenId.toNumber(),
      seller: i.seller,
      owner: i.owner,
      image: meta.data.image,
    }
    return item
  }))
  setNfts(items)
  setLoaded('loaded') 
}

Creating an NFT and placing it for sale is used to create NFT and put it on the market, including createToken and createMarketItem to create new tokens and place items for sale.

async function buyNft(nft) {
  const web3Modal = new Web3Modal({
    network: "mainnet",
    cacheProvider: true,
  });
  const connection = await web3Modal.connect()
  const provider = new ethers.providers.Web3Provider(connection)
  const signer = provider.getSigner()
  const contract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
  
  const price = web3.utils.toWei(nft.price.toString(), 'ether');
  
  const transaction = await contract.createMarketSale(nftaddress, nft.tokenId, {
    value: price
  })
  await transaction.wait()
  loadNFTs()
}

The key step is to allow users to buy NFT from the market

async function buyNft(nft) {
  const web3Modal = new Web3Modal({
    network: "mainnet",
    cacheProvider: true,
  });
  const connection = await web3Modal.connect()
  const provider = new ethers.providers.Web3Provider(connection)
  const signer = provider.getSigner()
  const contract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
  
  const price = web3.utils.toWei(nft.price.toString(), 'ether');
  
  const transaction = await contract.createMarketSale(nftaddress, nft.tokenId, {
    value: price
  })
  await transaction.wait()
  loadNFTs()
}

Allowing a user to view their own NFTs enables users to view the NFT they purchase and invokes the function from the fetchMyNFTs intelligent contract.

async function loadNFTs() {
  const web3Modal = new Web3Modal({
    network: "mainnet",
    cacheProvider: true,
  });
  const connection = await web3Modal.connect()
  const provider = new ethers.providers.Web3Provider(connection)
  const signer = provider.getSigner()
    
  const marketContract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
  const tokenContract = new ethers.Contract(nftaddress, NFT.abi, provider)
  const data = await marketContract.fetchMyNFTs()
  
  const items = await Promise.all(data.map(async i => {
    const tokenUri = await tokenContract.tokenURI(i.tokenId)
    const meta = await axios.get(tokenUri)
    let price = web3.utils.fromWei(i.price.toString(), 'ether');
    let item = {
      price,
      tokenId: i.tokenId.toNumber(),
      seller: i.seller,
      owner: i.owner,
      image: meta.data.image,
    }
    return item
  }))

  setNfts(items)
  setLoaded('loaded') 
}

Finally, we develop profitable projects in the trading market and charge listing fees from the transaction, which can be completed in only a few lines of code. First, open nftmarkets in Contracts Sol file, you need to set the price of using the listing, and you also need to create a variable to store the owner of the contract.
Can be in_ itemsSold initializes the following code:

address payable owner;
uint256 listingPrice = 0.01 ether;

Then create a constructor to store the address:

constructor() {
  owner = payable(msg.sender);
}

In the createMarketItem function, ensure that the listing fee includes the handling fee:

require(price > 0, "Price must be at least 1 wei"); // below this line 👇
require(msg.value == listingPrice, "Price must be equal to listing price");

Finally, in the createMarketSale function, listingPrice sends the value to the contract owner once the sale is completed. This can be placed at the end of the function:

payable(owner).transfer(listingPrice);

Next, in the client code, open pages / create - item JS and add the payment value to be sent with the transaction in the createSale function:

// above code omitted
const listingPrice = web3.utils.toWei('0.01', 'ether')

contract = new ethers.Contract(nftmarketaddress, Market.abi, signer)
transaction = await contract.createMarketItem(nftaddress, tokenId, price, { value: listingPrice })
// below code omitted

summary

As a non homogeneous currency, NFT has strong trading value. Building a digital trading market has good market prospects. The project can complete basic transaction functions, but there is still much room for improvement, including increasing the display of relevant products and protecting and optimizing payment links.

reference

  1. What is NFT? What platforms and projects does NFT have? NFT practice under Near (Mintbase)
  2. Awesome-NFT
  3. NFT built with verifiable RNG using Chainlink VRF

Keywords: Blockchain Bitcoin

Added by ayok on Wed, 26 Jan 2022 07:00:53 +0200