[SOLVED] Instant buy issue with adding item to marketplace [I CLONED RARABLE IN 24 HOURS pt11]

I am currently up to part 11 of the Rarible clone tutorial.

I am trying to make the ‘Instant Buy’ feature to my site.

I am able to mint my NFT, and get the approval, but cannot get the metamask popup to add the item to the marketplace.

Nothing initially happens after this is done, however, i refreshed the page and got this error:

Screenshot 2022-07-08 140011

main.js

const serverUrl = "https://hie...8ql.usemoralis.com:2053/server";
const appId = "OE9...Yjsr";
const TOKEN_CONTRACT_ADDRESS = "0x0d...301"
const MARKETPLACE_CONTRACT_ADDRESS = "0x5b...77f"

Moralis.start({ serverUrl, appId, TOKEN_CONTRACT_ADDRESS, MARKETPLACE_CONTRACT_ADDRESS });

init = async () => {
    window.onload = function() {
        if(!window.location.hash) {
            window.location = window.location + '#loaded';
            window.location.reload();
        }
    }
    hideElement(userProfileButton);
    hideElement(openCreateItemButton);
    hideElement(createItemForm);
    hideElement(openUserItemsButton);
    hideElement(downArrow);
    hideElement(userItemsSection);
    hideElement(userInfo);
    hideElement(createItemForm);
    hideElement(firstTip);
    hideElement(secondTip);
    showElement(checkMetaMask);
    showElement(checkUserLogIn)
    hideElement(userConnectButton);
    web3 = new Web3(window.ethereum);
    window.tokenContract = new web3.eth.Contract(tokenContractAbi, TOKEN_CONTRACT_ADDRESS);
    window.marketplaceContract = new web3.eth.Contract(marketplaceContractAbi, MARKETPLACE_CONTRACT_ADDRESS);

    document.body.style.overflow = 'hidden';
    web3.eth.getAccounts(function(err, accounts){
        if (err != null) {console.error("An error occurred: "+err);
    }else if (accounts.length == 0) {console.log("User is not logged in to MetaMask");
}else {
        console.log("User is logged in to MetaMask");
        hideElement(checkMetaMask);
        hideElement(checkUserLogIn);
        showElement(firstTip);
        showElement(userConnectButton);
        
    }  
    });
    await Moralis.enableWeb3();   
};
initUser = async () => {
    if (await Moralis.User.current()){
        hideElement(checkUserLogIn);
        hideElement(checkMetaMask);
        hideElement(firstTip);
        showElement(secondTip);
        hideElement(userConnectButton);
        showElement(userProfileButton);
        showElement(openCreateItemButton);
        showElement(openUserItemsButton);
        loadUserItems();
        showElement(downArrow);
    }else{
        hideElement(checkUserLogIn);
        hideElement(checkMetaMask);
        showElement(firstTip);
        hideElement(secondTip);
        showElement(userConnectButton);
        hideElement(userProfileButton);
        hideElement(openCreateItemButton);
        hideElement(createItemForm);
        hideElement(openUserItemsButton);
        hideElement(downArrow);
    }
}
login = async () => {
    try {
        await Moralis.authenticate();
        initUser();
        console.log("User has signed the signature request.");
        document.body.style.overflowY = 'scroll'; 
    } catch (error) {
        if (error.code == '4001'){
            alert(error.code + ": To connect your wallet to this site, you must confirm sign.");
        }
    }
}

disconnect = async () => {
    await Moralis.User.logOut();
    hideElement(userInfo);
    initUser();
    document.body.style.overflow = 'hidden';
    document.body.scrollTop = 0;
    document.documentElement.scrollTop = 0;
}
openUserInfo = async () => {
    
    user = await Moralis.User.current();
    if (user){    
        const email = user.get('email');
        if(email){
            userEmailField.value = email;
        }else{
            userEmailField.value = "";
        }
        userNameField.value = user.get('username');
        const userAvatar = user.get('avatar');
        if(userAvatar){
            userAvatarImg.src = userAvatar.url();
            showElement(userAvatarImg);
        }else{
            hideElement(userAvatarImg);
        }
        showElement(userInfo);
    }else{
        login();
    }
}

checkSaveUserInfo = async () => {
    if (userNameField.value.length ==0 && userEmailField.value.length ==0){
        alert("Please supply a name and email.");
        return;
    }else if (userEmailField.value.length ==0){
            alert("Please supply an email.");
            return;
    }else if (userNameField.value.length ==0){
            alert("Please supply a name.");
            return;
        }
    else{
        user.set('email', userEmailField.value);
        user.set('username', userNameField.value);
        
    }
    if (userAvatarFile.files.length > 0) {
        const avatar = new Moralis.File("avatar1.jpg", userAvatarFile.files[0]);
        user.set('avatar', avatar);
    }
try{
    await user.save();
}
 catch(error) {
    console.log(error);
    alert("Something went wrong. Please try again.");
    document.getElementById('txtEmail').value = '';
    return;
   }
    alert("User info saved successfully!");
    openUserInfo();
}

openUserItems = async () => {   
      user = await Moralis.User.current();
    if(user){
        showElement(userItemsSection);
    }else{
        login();
    }
}

loadUserItems = async () => {
    const params =  { token_address: TOKEN_CONTRACT_ADDRESS };
    const ownedItems = await Moralis.Cloud.run("getUserItems", params);
    ownedItems.forEach(item => {
        const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`);
        if (userItemListing) return;
        getAndRenderItemData(item, renderUserItem);
    });
}

initTemplate = (id) => {
    const template = document.getElementById(id);
    template.id = "";
    template.parentNode.removeChild(template);
    return template;
}

renderUserItem = (item) => {
    const userItem = userItemTemplate.cloneNode(true);
    userItem.getElementsByTagName("img")[0].src = item.image;
    userItem.getElementsByTagName("img")[0].alt = item.name;
    userItem.getElementsByTagName("h5")[0].innerText = item.name;
    userItem.getElementsByTagName("p")[0].innerText = item.description;
    userItems.appendChild(userItem);
    console.log(userItems);

}

getAndRenderItemData = (item, renderFunction) => {
    fetch(item.tokenUri)
    .then(response => response.json())
    .then(data => {
        item.name = data.name;
        item.description = data.description;
        item.image = data.image;
        renderFunction(item);
    })
}
ensureMarketplaceIsApproved = async (tokenId, tokenAddress) => {    
    user = await Moralis.User.current();
    const userAddress = user.get('ethAddress');
    const contract = new web3.eth.Contract(tokenContractAbi, tokenAddress);
    const approvedAddress = await contract.methods.getApproved(tokenId).call({from: userAddress});
    if (approvedAddress != MARKETPLACE_CONTRACT_ADDRESS){
        await contract.methods.approve(MARKETPLACE_CONTRACT_ADDRESS,tokenId).send({from: userAddress});
    }
}

createItem = async () => {
    if (createItemFile.files.length == 0){
        alert("Please select a file!");
        return;
    } else if (createItemNameField.value.length == 0){
        alert("Please give the item a name!");
        return;
    }
    mintNft = async (metadataUri) => {
        const receipt = await tokenContract.methods.createItem(metadataUri).send({ from: ethereum.selectedAddress });
        console.log(receipt);
        return receipt.events.Transfer.returnValues.tokenId;
    }

const nftFile = new Moralis.File("nftFile.webp", createItemFile.files[0]);
await nftFile.saveIPFS();

const nftFilePath = nftFile.ipfs();
const nftFileHash= nftFile.hash();

const metadata = {
    name: createItemNameField.value,
    description: createItemDescriptionField.value,
    image: nftFilePath,
};

const nftFileMetadataFile = new Moralis.File("metadata.json", {base64 : btoa(JSON.stringify(metadata))});
await nftFileMetadataFile.saveIPFS();

const nftFileMetadataFilePath = nftFileMetadataFile.ipfs();
const nftFileMetadataFileHash = nftFileMetadataFile.hash();

const nftId = await mintNft(nftFileMetadataFilePath);


const Item = Moralis.Object.extend("Item");

const item = new Item();
item.set('name', createItemNameField.value);
item.set('description', createItemDescriptionField.value);
item.set('nftFilePath', nftFilePath);
item.set('nftFileHash', nftFileHash);
item.set('metadataFilePath', nftFileMetadataFilePath);
item.set('metadataFileHash', nftFileMetadataFileHash);
item.set('nftId', nftId);
item.set('nftContractAddress', TOKEN_CONTRACT_ADDRESS);
await item.save();
alert("Item uploaded successfully! You may have to wait until you can view it in your inventory.");
console.log(item);

user = await Moralis.User.current();
const userAddress = user.get('ethAddress');

    switch(createItemStatusField.value){
        case "0":
            return;
            case "1":
                await ensureMarketplaceIsApproved(nftId, TOKEN_CONTRACT_ADDRESS);
                await marketplaceContract.methods.addItemToMarket(nftId, TOKEN_CONTRACT_ADDRESS, createItemPriceField.value);
                break;
                case "2":
                    alert("Not yet supported!");
                    return;
    }
}

hideElement = (element) => element.style.display = "none";
showElement = (element) => element.style.display = "block";

const firstTip = document.getElementById("tipConnect1");
const secondTip = document.getElementById("tipConnect2");
const downArrow = document.getElementById("arrow");
const checkMetaMask = document.getElementById("onload");


const userConnectButton = document.getElementById("btnConnect");
userConnectButton.onclick = login;

const userProfileButton = document.getElementById("btnUserInfo");
userProfileButton.onclick = openUserInfo;

const userInfo = document.getElementById("userInfo");
const userNameField = document.getElementById("txtUsername");
const userEmailField = document.getElementById("txtEmail");
const userAvatarImg = document.getElementById("imgAvatar");
const userAvatarFile = document.getElementById("fileAvatar");

document.getElementById("btnCloseUserInfo").onclick = () => hideElement(userInfo);
document.getElementById("btnDisconnect").onclick = disconnect;
document.getElementById("btnSaveUserInfo").onclick = checkSaveUserInfo;

const createItemForm = document.getElementById("createItem");

const createItemNameField = document.getElementById("txtCreateItemName");
const createItemDescriptionField = document.getElementById("txtCreateItemDescription");
const createItemPriceField = document.getElementById("numCreateItemPrice");
const createItemStatusField = document.getElementById("selectCreateItemStatus");
const createItemFile = document.getElementById("fileCreateItemFile");

// createItems
const openCreateItemButton = document.getElementById("btnOpenCreateItem");
openCreateItemButton.onclick = () => showElement(createItemForm);
document.getElementById("btnCloseCreateItem").onclick = () => hideElement(createItemForm);
document.getElementById("btnCreateItem").onclick = createItem;

// userItems
const userItemsSection = document.getElementById("userItems");
const userItems = document.getElementById("userItemsList");
document.getElementById("btnCloseUserItems").onclick = () => hideElement(userItemsSection);
const openUserItemsButton = document.getElementById("btnMyItems");
openUserItemsButton.onclick = openUserItems;

const userItemTemplate = initTemplate("itemTemplate");

init();

Marketplace.sol:

pragma solidity ^0.8.0;
//SPDX-License-Identifier: UNLICENSED
import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";

contract MorarableMarketContract {
    struct AuctionItem {
        uint256 id;
        address tokenAddress;
        uint256 tokenId;
        address payable seller;
        uint256 askingPrice;
        bool isSold;
    }

    AuctionItem[] public itemsForSale;
    mapping (address => mapping (uint256 => bool)) activeItems;

event itemAdded(uint256 id, uint256 tokenId, address tokenAddress, uint256 askingPrice);
event itemSold(uint256 id, address buyer, uint256 askingPrice);

modifier OnlyItemOwner(address tokenAddress, uint256 tokenId){
    IERC721 tokenContract = IERC721(tokenAddress);
    require(tokenContract.ownerOf(tokenId) == msg.sender);
    _;
}
modifier HasTransferApproval(address tokenAddress, uint256 tokenId){
    IERC721 tokenContract = IERC721(tokenAddress);
    require(tokenContract.getApproved(tokenId) == address(this));
    _;
}
modifier ItemExists(uint256 id){
    require(id < itemsForSale.length && itemsForSale[id].id == id, "could not find them");
    _;
}
modifier IsForSale(uint256 id){
    require(itemsForSale[id].isSold == false, "Item is already sold!");
    _;
}
function addItemToMarket(uint256 tokenId, address tokenAddress, uint256 askingPrice) OnlyItemOwner(tokenAddress,tokenId) HasTransferApproval(tokenAddress,tokenId) external returns (uint256){
    require(activeItems[tokenAddress][tokenId] == false, "Item is already up for sale!");
    uint256 newItemId = itemsForSale.length;
    itemsForSale.push(AuctionItem(newItemId, tokenAddress, tokenId, payable(msg.sender), askingPrice, false));
    activeItems[tokenAddress][tokenId] = true;


assert(itemsForSale[newItemId].id == newItemId);
emit itemAdded(newItemId, tokenId, tokenAddress, askingPrice);
return newItemId;
}

function buyItem(uint256 id) payable external ItemExists(id) IsForSale(id)HasTransferApproval(itemsForSale[id].tokenAddress,itemsForSale[id].tokenId){
    require(msg.value >= itemsForSale[id].askingPrice, "Not enough funds sent");
    require(msg.sender != itemsForSale[id].seller);

    itemsForSale[id].isSold = true;
    activeItems[itemsForSale[id].tokenAddress][itemsForSale[id].tokenId] = false;
    IERC721(itemsForSale[id].tokenAddress).safeTransferFrom(itemsForSale[id].seller, msg.sender, itemsForSale[id].tokenId);
    itemsForSale[id].seller.transfer(msg.value);

    emit itemSold(id, msg.sender,itemsForSale[id].askingPrice);
}
}
1 Like

After refreshing it again, i received this error that hasnt shown up again after next refresh:

Screenshot 2022-07-08 145900

Do you still have the error while fetching ipfs?

can you share which function is causing the second error?

I am no longer getting any errors but still not working. I havent changed anything to my code after these

according to the second and third error it is fetch(item.tokenUri) in this function:

getAndRenderItemData = (item, renderFunction) => {
    fetch(item.tokenUri)
    .then(response => response.json())
    .then(data => {
        item.name = data.name;
        item.description = data.description;
        item.image = data.image;
        renderFunction(item);
    })
}

and

loadUserItems = async () => {
    const params =  { token_address: TOKEN_CONTRACT_ADDRESS };
    const ownedItems = await Moralis.Cloud.run("getUserItems", params);
Error 162 -->    ownedItems.forEach(item => {
        const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`);
        if (userItemListing) return;
Error 165-->        getAndRenderItemData(item, renderUserItem);
    });
}

Try console.log(ownedItems) to check if there is a data returned from cloud function. I guess it is returning an empty data or undefined, which is causing the error.

I now get this error. Ignore the one on line 206.

Screenshot 2022-07-08 163022

error on Line 266 within the createItem function:

switch(createItemStatusField.value){
        case "0":
            return;
            case "1":
                await ensureMarketplaceIsApproved(nftId, TOKEN_CONTRACT_ADDRESS);
                await marketplaceContract.methods.addItemToMarket(nftId, TOKEN_CONTRACT_ADDRESS, createItemPriceField.value);
line 266 -->    break;
                case "2":
                    alert("Not yet supported!");
                    return;
    }

actually, reloading back onto the site came up with the ipfs error again:

but coming back to this, it returns:

What is in line 206? The error is caused at ownedItems variable.

Check the code in line 188. There is an error caused during the ipfs URL fetch. You can change the ipfs gateway to ipfs.moralis.io/ipfs/ or ipfs.io/ipfs and run the fetch function again.

error on line 206 is here:

ensureMarketplaceIsApproved = async (tokenId, tokenAddress) => { 
    user = await Moralis.User.current();
    const userAddress = user.get('ethAddress');
    const contract = new web3.eth.Contract(tokenContractAbi, tokenAddress);
    const approvedAddress = await contract.methods.getApproved(tokenId).call({from: userAddress});
    if (approvedAddress != MARKETPLACE_CONTRACT_ADDRESS){
        await contract.methods.approve(MARKETPLACE_CONTRACT_ADDRESS,tokenId).send({from: userAddress});
    }
}           <--line 206

the ownedItems variables are located here on lines 159 to 168

loadUserItems = async () => {
    const params =  { token_address: TOKEN_CONTRACT_ADDRESS };
    const ownedItems = await Moralis.Cloud.run("getUserItems", params);
    ownedItems.forEach(item => {
        const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`);
        if (userItemListing) return;
        getAndRenderItemData(item, renderUserItem);
    });
    console.log(ownedItems)
}

How do i change the IPFS gateway exactly?

I fixed my issue.

I added .send({from: userAddress }); to the end of await marketplaceContract.methods.addItemToMarket method that corresponds with const userAddress = user.get('ethAddress'); before the function starts. I also removed a wrongly placed console.log action. These seemed to remove all errors.

1 Like