Rarible clone - cloud functions suddenly not working

Iā€™ve been trying to figure this out for a little while but canā€™t make sense of it. I thought I had the cloud functions and plugins working properly, however when I got to the point of rendering ItemsFor Sale from the getItems function, I noticed that the ā€œuserā€ and ā€œtokenā€ outputs from the ā€œItemsForSaleā€ cloud function were the same. I had accidentally written

const userObject = await query.first({ useMasterKey: true });

which I then changed to:

const userObject = await userQuery.first({ useMasterKey: true });

But now when I create a new item, the ā€œitemsForSaleā€ table is not updated with the new items.

Iā€™m stumped, and have maybe stared at it for too long to find a solution.

I will include all relevant code below, note 2 things:

  1. I commented out some code from main.js that came after the point that I am stuck at to avoid getting errors.

2)I intentionally wrote ā€œitemsForSaleā€ instead of"ItemsForSale" in the cloud function because thatā€™s how I wrote it in the plug in and didnā€™t wnt to rewrite it.

Thanks for all your help! Moralis rules!

Moralis.Cloud.define("getUserItems", async (request) => {
    const query = new Moralis.Query("EthNFTTokenOwners");
    query.equalTo("contract_type", "ERC721");
    query.containedIn("owner_of", request.user.attributes.accounts);
    const queryResults = await query.find();
    const results = [];
    for (let i = 0; i < queryResults.length; ++i) {
        results.push({
            "id": queryResults[i].attributes.objectId,
            "tokenId": queryResults[i].attributes.token_id,
            "tokenAddress": queryResults[i].attributes.token_address,
            "symbol": queryResults[i].attributes.symbol,
            "tokenUri": queryResults[i].attributes.token_uri,
        });
    }
    return results;
});

Moralis.Cloud.beforeSave("itemsForSale", async (request) => {
    const query = new Moralis.Query("EthNFTTokenOwners");
    query.equalTo("token_address", request.object.get('tokenAddress'));
    query.equalTo("token_id", request.object.get('tokenId'));
    const object = await query.first();
    if (object) {
        const owner = object.attributes.owner_of;
        const userQuery = new Moralis.Query(Moralis.User);
        userQuery.equalTo('accounts', owner);
        const userObject = await userQuery.first({ useMasterKey: true });
        if (userObject) {
            request.object.set('user', userObject);
        }
        request.object.set('token', object)
    }
});




Moralis.initialize("9quLXz9G6OoV6LXrakGsZQNPvCiTu9Fo5NJ0DN7y");
Moralis.serverURL = "https://sqmfav2bsl7q.moralis.io:2053/server";

const TOKEN_CONTRACT_ADDRESS = "0x34b35FdE3f6ed6130E064A3d42b966e7c714Caf7";
const MARKETPLACE_CONTRACT_ADDRESS =
  "0xC34988D20271ddB0e601B6b25937E02da0AFBb7E";

//Nav bar
const userInfo = document.getElementById("userInfo");
const userConnectButton = document.getElementById("btnConnect");
const userProfileButton = document.getElementById("btnUserInfo");

//User Profile
const userUsernameField = document.getElementById("txtUsername");
const userEmailField = document.getElementById("txtEmail");
const userAvatarFile = document.getElementById("fileAvatar");
const userAvatarImg = document.getElementById("imgAvatar");
const openCreateItemButton = document.getElementById("btnOpenCreateItem");
const closeCreateItemButton = document.getElementById("btnCloseCreateItem");
const createItemForm = document.getElementById("createItem");

//Upload item
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");

init = async () => {
  hideElement(userItemsSection);
  hideElement(userInfo);
  hideElement(createItemForm);
  window.web3 = await Moralis.Web3.enable();
  window.tokenContract = new web3.eth.Contract(
    tokenContractAbi,
    TOKEN_CONTRACT_ADDRESS
  );
  window.marketplaceContract = new web3.eth.Contract(
    marketplaceContractAbi,
    MARKETPLACE_CONTRACT_ADDRESS
  );

  initUser();
  // loadItems();
};

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;
  }

  const nftFile = new Moralis.File("nftFile.jpg", 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();
  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
      ).send({ from: userAddress });
      break;
    case "2":
      alert("Not yet supported");
      return;
  }
};

mintNft = async (metadataUrl) => {
  const receipt = await tokenContract.methods
    .createItem(metadataUrl)
    .send({ from: ethereum.selectedAddress });
  console.log(receipt);
  return receipt.events.Transfer.returnValues.tokenId;
};

hideElement = (e) => {
  e.style.display = "none";
};

showElement = (e) => {
  e.style.display = "block";
};

initUser = async () => {
  if (await Moralis.User.current()) {
    hideElement(userConnectButton);
    showElement(userProfileButton);
    showElement(openCreateItemButton);
    showElement(openUserItemsBtn);
    loadUserItems();
  } else {
    showElement(userConnectButton);
    hideElement(userProfileButton);
    hideElement(openCreateItemButton);
  }
};

login = async () => {
  try {
    await Moralis.Web3.authenticate();
    initUser();
  } catch (error) {
    alert(error);
  }
};

userConnectButton.onclick = login;

openUserInfo = async () => {
  user = await Moralis.User.current();
  if (user) {
    const email = user.get("email");
    if (email) {
      userEmailField.value = email;
    } else {
      userEmailField.value = "";
    }

    userUsernameField.value = user.get("username");

    const userAvatar = user.get("avatar");
    if (userAvatar) {
      userAvatarImg.src = userAvatar.url();
      showElement(userAvatarImg);
    } else {
      hideElement(userAvatarImg);
    }
    showElement(userInfo);
  } else {
    login();
  }
};

userProfileButton.onclick = openUserInfo;

logout = async () => {
  await Moralis.User.logOut();
  hideElement(userInfo);
  initUser();
};

document.getElementById("btnCloseUserInfo").onclick = () =>
  hideElement(userInfo);

document.getElementById("btnLogout").onclick = logout;

saveUserInfo = async () => {
  user.set("email", userEmailField.value);
  user.set("username", userUsernameField.value);

  if (userAvatarFile.files.length > 0) {
    const avatar = new Moralis.File("avatar.jpg", userAvatarFile.files[0]);
    user.set("avatar", avatar);
  }

  await user.save();
  alert("User info saved successfully!");
  openUserInfo();
};

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

loadUserItems = async () => {
  const ownedItems = await Moralis.Cloud.run("getUserItems");
  ownedItems.forEach((item) => {
    getAndRenderItemData(item, renderUserItem);
  });
};

// loadItems = async () => {
//   const items = await Moralis.Cloud.run('getItems')
//   items.forEach(item => {
//     getAndRenderItemData(item, renderItem);
//   })
// }

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);
};

// renderItem = (item) => {
//   const itemForSale = marketplaceItemTemplate.cloneNode(true);
//   if (item.sellerAvatar) {
//     itemForSale.getElementsByTagName("img")[0].src = item.sellerAvatar.url();
//     itemForSale.getElementsByTagName("img")[0].alt = item.sellerUsername;
//   }
//   itemForSale.getElementsByTagName("img")[1].src = item.image;
//   itemForSale.getElementsByTagName("img")[1].alt = item.name;
//   itemForSale.getElementsByTagName("h5")[0].innerText = item.name;
//   itemForSale.getElementsByTagName("p")[0].innerText = item.description;

//   itemForSale.getElementsByTagName("button")[0].innerText = `Buy for ${item.askingPrice}`;

//   itemForSale.id = `item-${item.uid}`
//   itemsForSale.appendChild(itemForSale);
// };

getAndRenderItemData = (item, renderFunction) => {
  fetch(item.tokenUri)
    .then((res) => res.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 });
  }
};

document.getElementById("btnCreateItem").onclick = createItem;

document.getElementById("btnSaveUserInfo").onclick = saveUserInfo;

openCreateItemButton.onclick = () => showElement(createItemForm);

closeCreateItemButton.onclick = () => hideElement(createItemForm);

//user items

const userItemsSection = document.getElementById("userItems");
const userItems = document.getElementById("userItemsList");

const openUserItemsBtn = document.getElementById("btnMyItems");
const closeUserItemsBtn = document.getElementById("btnCloseUserItems");
openUserItemsBtn.onclick = openUserItems;

closeUserItemsBtn.onclick = () => hideElement(userItemsSection);

const userItemTemplate = initTemplate("itemTemplate");

const marketplaceItemTemplate = initTemplate("marketplaceItemTemplate");

//Items for sale
const itemsForSale = document.getElementById('itemsForSale')

init();

I should mention that newly created items do appear in the ā€œItemsā€ table, but they fail to appear in the ā€œitemsForSaleā€ table.

Hi @mugshot,

Did you make sure to update your contract address on the plugin configuration? Usually when we redeploy the contract locally, we forget to update the plugin contract address, we usually only remember to update the frontend variables with the new contract address.

This was an issue from my side as well. Do let me know if that was the root cause.

Thanks

Thanks for your response! Both plugins are up to date with the present marketplace contract address. I think this is correct, unless Iā€™m wrong and it is actually supposed to contain the token contract address instead. I am currently under the impression that the marketplace contract address is the relevant one though, but I may try it both ways anyway just to try something, lol.

Yes, it should be the current market contract address. Not the token contract address.

If this didnā€™t work, please try recreating a new server and copy/paste the cloud functions from your existing server to the new one. Along with plugins. (This time keep the name of ā€œitemsForSaleā€ as ā€œItemsForSaleā€)This usually solves the issue, but if it didnā€™t Iā€™ll escalate the issue to Nicolas for further diagnosis.

Thanks.

1 Like

Still not working unfortunately. Maybe is there something I am missing for using the ā€œuseMasterKeyā€ code? Or does it pull the master key straight from the server without me having to code it elsewhere?

Thanks for all the help, much appreciated!

You donā€™t need to specify the masterKey, useMasterKey is enough.
Have you made sure to change the names of the tables after the latest releases with breaking changes.
For example, it is no longer called ā€œEthNFTTokenOwnersā€ but, EthNFTOwners.
If you used the old one, you would get not result and it would not give the expected outcome.
Also can you confirm that the event has been fired if you look at the logs in Ganache?

I started a new server instance one more time, changed the query table to ā€œEthNFTOwnersā€ and remade the plugins, and it seems to be working properly now! Thanks again for all the help!

1 Like