Hi everyone!
I try create NFT marketplace, where i can buy NFT with erc20 token.
MyMarket.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
// security against transactions for multiple requests
import "hardhat/console.sol";
import "./OurToken.sol";
import "./NFT.sol";
contract KBMarket is ReentrancyGuard {
using Counters for Counters.Counter;
OurToken private ourtoken;
/* number of items minting, number of transactions, tokens that have not been sold
keep track of tokens total number - tokenId
arrays need to know the length - help to keep track for arrays */
Counters.Counter private _tokenIds;
Counters.Counter private _tokensSold;
// determine who is the owner of the contract
// charge a listing fee so the owner makes a commission
address payable owner;
// we are deploying to matic the API is the same so you can use ether the same as matic
// they both have 18 decimal
// 0.045 is in the cents
uint256 listingPrice = 0.045 ether;
modifier isMyToken(address _token) {
require(_token == address(ourtoken), "Not my token");
_;
}
constructor(address tokenaddress) {
//set the owner
owner = payable(msg.sender);
ourtoken = OurToken(tokenaddress);
}
// structs can act like objects
struct MarketToken {
uint256 itemId;
address nftContract;
uint256 tokenId;
address payable seller;
address payable owner;
uint256 price;
bool sold;
}
// tokenId return which MarketToken - fetch which one it is
mapping(uint256 => MarketToken) private idToMarketToken;
// listen to events from front end applications
event MarketTokenMinted(
uint256 indexed itemId,
address indexed nftContract,
uint256 indexed tokenId,
address seller,
address owner,
uint256 price,
bool sold
);
// get the listing price
function getListingPrice() public view returns (uint256) {
return listingPrice;
}
// two functions to interact with contract
// 1. create a market item to put it up for sale
// 2. create a market sale for buying and selling between parties
function makeMarketItem(
address nftContract,
uint256 tokenId,
uint256 price
) public payable nonReentrant {
// nonReentrant is a modifier to prevent reentry attack
require(price > 0, "Price must be at least one wei");
require(
msg.value == listingPrice,
"Price must be equal to listing price"
);
_tokenIds.increment();
uint256 itemId = _tokenIds.current();
//putting it up for sale - bool - no owner
idToMarketToken[itemId] = MarketToken(
itemId,
nftContract,
tokenId,
payable(msg.sender),
payable(address(0)),
price,
false
);
// NFT transaction
IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
emit MarketTokenMinted(
itemId,
nftContract,
tokenId,
msg.sender,
address(0),
price,
false
);
}
// function to conduct transactions and market sales
function createMarketSale(address nftContract, uint256 itemId)
public
nonReentrant
{
uint256 price = idToMarketToken[itemId].price;
uint256 value = price * (10**ERC20(ourtoken).decimals());
uint256 tokenId = idToMarketToken[itemId].tokenId;
// transfer the amount to the seller
ERC20(ourtoken).transferFrom(msg.sender, address(this), value);
IERC721(nftContract).transferFrom(address(this), msg.sender, tokenId);
_tokensSold.increment();
//payable(owner).transfer(listingPrice);
}
// function to fetchMarketItems - minting, buying ans selling
// return the number of unsold items
function fetchMarketTokens() public view returns (MarketToken[] memory) {
uint256 itemCount = _tokenIds.current();
uint256 unsoldItemCount = _tokenIds.current() - _tokensSold.current();
uint256 currentIndex = 0;
// looping over the number of items created (if number has not been sold populate the array)
MarketToken[] memory items = new MarketToken[](unsoldItemCount);
for (uint256 i = 0; i < itemCount; i++) {
if (idToMarketToken[i + 1].owner == address(0)) {
uint256 currentId = i + 1;
MarketToken storage currentItem = idToMarketToken[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
// return nfts that the user has purchased
function fetchMyNFTs() public view returns (MarketToken[] memory) {
uint256 totalItemCount = _tokenIds.current();
// a second counter for each individual user
uint256 itemCount = 0;
uint256 currentIndex = 0;
for (uint256 i = 0; i < totalItemCount; i++) {
if (idToMarketToken[i + 1].owner == msg.sender) {
itemCount += 1;
}
}
// second loop to loop through the amount you have purchased with itemcount
// check to see if the owner address is equal to msg.sender
MarketToken[] memory items = new MarketToken[](itemCount);
for (uint256 i = 0; i < totalItemCount; i++) {
if (idToMarketToken[i + 1].owner == msg.sender) {
uint256 currentId = idToMarketToken[i + 1].itemId;
// current array
MarketToken storage currentItem = idToMarketToken[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
// function for returning an array of minted nfts
function fetchItemsCreated() public view returns (MarketToken[] memory) {
// instead of .owner it will be the .seller
uint256 totalItemCount = _tokenIds.current();
uint256 itemCount = 0;
uint256 currentIndex = 0;
for (uint256 i = 0; i < totalItemCount; i++) {
if (idToMarketToken[i + 1].seller == msg.sender) {
itemCount += 1;
}
}
// second loop to loop through the amount you have purchased with itemcount
// check to see if the owner address is equal to msg.sender
MarketToken[] memory items = new MarketToken[](itemCount);
for (uint256 i = 0; i < totalItemCount; i++) {
if (idToMarketToken[i + 1].seller == msg.sender) {
uint256 currentId = idToMarketToken[i + 1].itemId;
MarketToken storage currentItem = idToMarketToken[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
}
NFT.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
// we will bring in the openzeppelin ERC721 NFT functionality
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract NFT is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
// counters allow us to keep track of tokenIds
// address of marketplace for NFTs to interact
address contractAddress;
// OBJ: give the NFT market the ability to transact with tokens or change ownership
// setApprovalForAll allows us to do that with contract address
// constructor set up our address
constructor(address marketplaceAddress) ERC721("KryptoBirdz", "KBIRDZ") {
contractAddress = marketplaceAddress;
}
function mintToken(string memory tokenURI) public returns (uint256) {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(msg.sender, newItemId);
// set the token URI: id and url
_setTokenURI(newItemId, tokenURI);
// give the marketplace the approval to transact between users
setApprovalForAll(contractAddress, true);
// mint the token and set it for sale - return the id to do so
return newItemId;
}
}
Token.sol
// contracts/OurToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract OurToken is ERC20 {
constructor() ERC20("Liver", "LIV") {
_mint(0xbDA5747bFD65F08deb54cb465eB87D40e51B197E, 1000000E18);
}
}
deploy.js
const hre = require("hardhat");
const fs = require('fs')
async function main() {
const Token = await hre.ethers.getContractFactory("OurToken");
const token = await Token.deploy();
await token.deployed();
console.log("token contract deployed to: ", token.address);
const NFTMarket = await hre.ethers.getContractFactory("KBMarket");
const nftMarket = await NFTMarket.deploy(token.address);
await nftMarket.deployed();
console.log("nftMarket contract deployed to: ", nftMarket.address);
const NFT = await hre.ethers.getContractFactory("NFT");
const nft = await NFT.deploy(nftMarket.address);
await nft.deployed();
console.log("NFT contract deployed to: ", nftMarket.address);
let config = `
export const nftmarketaddress = ${nftMarket.address}
export const nftaddress = ${nft.address}
export const tokenaddress = ${token.address}`
let data = JSON.stringify(config)
fs.writeFileSync('config.js', JSON.parse(data))
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
function byu nft on my index.js
async function buyNFT(nft) {
try {
const web3Modal = new Web3Modal()
const connection = await web3Modal.connect()
const provider = new providers.Web3Provider(window.ethereum)
await provider.send("eth_requestAccounts", []);
const signer = provider.getSigner()
console.log(signer)
const contract = new ethers.Contract(nftmarketaddress, KBMarket.abi, signer)
const price = ethers.utils.parseUnits(nft.price.toString(), 'ether')
const token = new ethers.Contract(tokenaddress, OurToken.abi, signer)
await token.approve(nftmarketaddress, ethers.utils.parseEther(nft.price.toString()))
console.log(nft.price.toString())
const signerAddress = await signer.getAddress()
console.log(signerAddress)
console.log((await token.balanceOf(signerAddress)).toString())
const transaction = await contract.createMarketSale(nftaddress, nft.tokenId)
await transaction.wait()
loadNFTs()
} catch (err) { console.log(err) }
}
When i click buy on my site, i have error on my console:
MetaMask - RPC Error: Internal JSON-RPC error
message: "Error: VM Exception while processing transaction: reverted with reason string 'ERC20: insufficient allowance'"
I will be grateful for your help