Hi everyone,
I'm trying to build fraction NFT Marketplace, It's now working when I try to create a new NFT and show it in the frontend.
I'm now facing an error in my buyNFT function and no matter what I try it keeps happening,
(reason="Error: VM Exception while processing transaction: reverted with reason string 'Not enough Ether sent to buy the shares.'", method="estimateGas",
can anyone guide me on what I should do?
this is my smart contract right now:
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "./CustomERC20.sol";
contract FractionalNFTMarketplace is ERC721URIStorage, Ownable {
using Strings for uint256;
uint256 private _currentTokenId = 0;
address public customERC20Address;
struct FractionalToken {
CustomERC20 token;
uint256 totalShares;
uint256 pricePerShare;
}
struct MarketItem {
uint256 tokenId;
address payable seller;
address payable owner;
uint256 totalShares;
uint256 pricePerShare;
bool isForSale;
}
mapping(uint256 => FractionalToken) private _fractionalTokens;
mapping(uint256 => mapping(address => uint256)) private _shareOwners;
mapping(uint256 => MarketItem) private _marketItems;
Counters.Counter private _marketItemId;
event TokenCreated(uint256 indexed tokenId, uint256 totalShares, uint256 pricePerShare);
event TokenBought(uint256 indexed tokenId, address indexed buyer, uint256 shares);
event MarketItemCreated(uint256 indexed tokenId, address indexed seller, uint256 totalShares, uint256 pricePerShare);
event MarketItemSold(uint256 indexed marketItemId, address indexed buyer);
event ApprovalForToken(uint256 indexed tokenId, address indexed owner, uint256 amount);
constructor(address _customERC20Address) ERC721("Fractional NFT Marketplace", "FNFTM") {
customERC20Address = _customERC20Address;
}
function createMarketItem(uint256 tokenId) private {
require(_exists(tokenId), "Token ID does not exist.");
address currentOwner = ownerOf(tokenId);
FractionalToken storage fractionalToken = _fractionalTokens[tokenId];
_marketItems[tokenId] = MarketItem(
tokenId,
payable(currentOwner),
payable(address(this)),
fractionalToken.totalShares,
fractionalToken.pricePerShare,
true
);
emit MarketItemCreated(tokenId, currentOwner, fractionalToken.totalShares, fractionalToken.pricePerShare);
}
function createToken(uint256 totalShares, uint256 pricePerShare, string memory tokenURI) public payable returns (uint256) {
require(totalShares > 0, "Total shares must be greater than 0.");
require(pricePerShare > 0, "Price per share must be greater than 0.");
uint256 newTokenId = _currentTokenId++;
_mint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI);
string memory name = string(abi.encodePacked("Shares of NFT #", newTokenId.toString()));
string memory symbol = string(abi.encodePacked("SNFT", newTokenId.toString()));
CustomERC20 newERC20Token = new CustomERC20(name, symbol);
newERC20Token.mint(msg.sender, totalShares);
_fractionalTokens[newTokenId] = FractionalToken(newERC20Token, totalShares, pricePerShare);
_shareOwners[newTokenId][msg.sender] = totalShares;
approveContractForToken(newTokenId,totalShares);
createMarketItem(newTokenId);
emit TokenCreated(newTokenId, totalShares, pricePerShare);
return newTokenId;
}
function approveContractForToken(uint256 tokenId, uint256 amount) public {
require(_exists(tokenId), "Token ID does not exist.");
require(ownerOf(tokenId) == msg.sender, "Caller is not the owner of the token.");
FractionalToken storage fractionalToken = _fractionalTokens[tokenId];
fractionalToken.token.approve(address(this), amount);
emit ApprovalForToken(tokenId, msg.sender, amount);
}
function getAllowance(uint256 tokenId, address owner, address spender) public view returns (uint256) {
require(_exists(tokenId), "Token ID does not exist.");
FractionalToken storage fractionalToken = _fractionalTokens[tokenId];
return fractionalToken.token.allowance(owner, spender);
}
function buyToken(uint256 tokenId,uint256 totalPrice, uint256 shares) public payable {
require(_exists(tokenId), "Token ID does not exist.");
require(shares > 0, "Shares must be greater than 0.");
FractionalToken storage fractionalToken = _fractionalTokens[tokenId];
require(fractionalToken.totalShares >= shares, "Not enough shares available.");
// uint256 totalPrice = fractionalToken.pricePerShare * shares;
require(msg.value >= totalPrice, "Not enough Ether sent to buy the shares.");
address seller = ownerOf(tokenId);
CustomERC20 customERC20 = CustomERC20(customERC20Address);
uint256 allowance = customERC20.allowance(seller, address(this));
require(allowance >= shares, "Not enough allowance to transfer shares.");
customERC20.transferFrom(seller, msg.sender, shares);
payable(seller).transfer(totalPrice);
fractionalToken.totalShares -= shares;
_shareOwners[tokenId][seller] -= shares;
_shareOwners[tokenId][msg.sender] += shares;
emit TokenBought(tokenId, msg.sender, shares);
}
This is my context.js file that connects it with the front-end:
const CreateNft = async (name, price, shares, image, description, router) => {
if (!name || !description || !price || !image || !shares)
return console.log("Data is Missing!");
const data = JSON.stringify({ name, description, image });
try {
const added = await client.add(data);
const url = `https://infura-ipfs.io/ipfs/${added.path}`;
await createSale(url, price, shares);
} catch (error) {
console.log(error);
}
};
//Create Sale
const createSale = async (url, Price, shares, isResale, id) => {
try {
const price = ethers.utils.parseUnits(Price, "ether");
const contract = await SmartContractConnect();
const transaction = !isResale
? await contract.createToken(shares, price, url)
: await contract.resellToken(id, price);
await transaction.wait();
console.log("Transaction Completed: ", transaction);
} catch (error) {
console.log("Error while creating sale: ", error);
}
};
//Fetch NFTS
const FetchNFTs = async () => {
try {
const provider = new ethers.providers.JsonRpcProvider();
const contract = fetchSmartContract(provider);
const data = await contract.fetchMarketItems();
// console.log("Data: ", data);
const items = await Promise.all(
data.map(async ({ tokenId, seller,owner, pricePerShare,totalShares }) => {
const TokenURI = await contract.tokenURI(tokenId);
const {
data: { image, name, description },
} = await axios.get(TokenURI);
const price = ethers.utils.formatUnits(pricePerShare.toString(), "ether");
return {
price,
tokenId: tokenId.toNumber(),
seller,
owner,
image,
name,
description,
TokenURI,
totalShares: totalShares.toNumber()
};
})
);
// console.log("Fetch success: ", items);
return items;
} catch (error) {
console.log("Error while fetching NFTs: ", error);
}
};
useEffect(()=>{
FetchNFTs();
},[]);
//Buy NFTs
const BuyNFT = async (NFT, shares) => {
try {
const contract = await SmartContractConnect();
const price = NFT.price*(shares)
const totalPrice = ethers.utils.parseUnits(price.toString(), "ether");
const transaction = await contract.buyToken(NFT.tokenId,totalPrice, shares);
await transaction.wait();
router.push("/profile");
} catch (error) {
console.log("Error while buying NFT", error);
}
};