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.
Error: VM Exception while processing transaction: reverted with reason string 'ERC20: insufficient allowance'
can anyone guide me on what I should do?
this is my smart contract right now:
// SPDX-License-Identifier: MIT
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,address(this));
createMarketItem(newTokenId);
newERC20Token.approve(address(this), totalShares);
emit TokenCreated(newTokenId, totalShares, pricePerShare);
return newTokenId;
}
function approveContractForToken(uint256 tokenId, uint256 amount, address spender) 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(spender, 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.");
require(msg.value >= totalPrice, "Not enough Ether sent to buy the shares.");
address seller = ownerOf(tokenId);
CustomERC20 token = fractionalToken.token;
uint256 allowance = token.allowance(seller, address(this));
//require(allowance >= shares, "Not enough allowance to transfer shares.");
token.transferFrom(seller, msg.sender, shares);
payable(seller).transfer(totalPrice);
fractionalToken.totalShares -= shares;
_shareOwners[tokenId][seller] -= shares;
_shareOwners[tokenId][msg.sender] += shares;
// Re-approve the remaining allowance
uint256 remainingAllowance = allowance - shares;
approveContractForToken(tokenId, remainingAllowance, address(this)); // Call the function to re-approve the remaining allowance
emit TokenBought(tokenId, msg.sender, shares);
}
This is my context.js file that connects it with the front-end:
const ApproveShares = async (NFT, shares) => {
try {
const contract = await SmartContractConnect();
const customERC20Address = await contract.customERC20Address();
const customERC20 = new ethers.Contract(customERC20Address, CustomERC20ABI, contract.signer);
const totalPrice = ethers.utils.parseUnits((NFT.price * shares).toString(), "ether");
console.log("Approving shares:", {
tokenId: NFT.tokenId,
shares,
totalPrice,
});
const transaction = await customERC20.approve(contract.address, totalPrice);
await transaction.wait();
const allowance = await customERC20.allowance(contract.signer.getAddress(), contract.address);
console.log("Allowance set:", {
allowance: ethers.utils.formatUnits(allowance.toString(), "ether"),
});
console.log("Shares approved for transfer");
} catch (error) {
console.log("Error while approving shares", error);
}
};
//Create NFt
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}`;
//new
const tokenId = await createSale(url, price, shares);
await ApproveShares({ tokenId, 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);
//new
const receipt = await transaction.wait(); // Get the transaction receipt
const tokenId = receipt.events.find(event => event.event === "TokenCreated").args.tokenId; // Find the tokenId from the "TokenCreated" event
console.log("Transaction Completed: ", transaction);
return tokenId; // Return the tokenId
//await transaction.wait();
} catch (error) {
console.log("Error while creating sale: ", error);
}
};
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,{
gasLimit: 5000000,
gasPrice: 10000000000,
value: totalPrice
});
await transaction.wait();
router.push("/profile");
} catch (error) {
console.log("Error while buying NFT", error);
}
};