I am almost 2 weeks trying to debug the sold item event + isSold flag. when It is being sold I donāt see it in the SoldItems table + canāt see new Columb with isSold true in itemsForSale after an item was sold, if anyone can help me to debug it, it would be HIGHLY appreciated.
/**************** Initialize the app ******************/
init = async () => {
$('#loading').fadeOut(1500);
$('.close-modal_event').on('click', function(e) {
e.preventDefault();
$('.modal').toggleClass('is-visible');
});
$(".header_wallet-toggle").on('click', function(e) {
e.preventDefault();
$('.modal').toggleClass('is-visible');
});
$('#userInfoDiv').css("display", "none");
$('#createItem').css("display", "none");
$('#userItems').css("display", "none");
const web3 = await Moralis.enableWeb3();
window.tokenContract = new web3.eth.Contract(tokenContractAbi, TOKEN_CONTRACT_ADDRESS);
window.marketplaceContract = new web3.eth.Contract(marketplaceContractAbi, MARKETPLACE_CONTRACT_ADDRESS);
// Get Current USD Price
let ws = new WebSocket('wss://stream.binance.com:9443/ws/ethusdt@trade')
ws.onmessage = async (event) => {
let stockObject = JSON.parse(event.data)
price = await stockObject.p;
price = parseFloat(price).toFixed(2);
ws.close();
$('#itemsLoader').show();
setTimeout(async () => {
$('#itemsLoader').hide();
loadItems(price);
passPriceToCreateItem(price);
}, 100)
}
initUser();
const soldItemsQuery = new Moralis.Query('SoldItems');
const soldItemsSubscription = await soldItemsQuery.subscribe();
soldItemsSubscription.on("create", onItemSold);
const itemsAddedQuery = new Moralis.Query('ItemsForSale');
const itemsAddedSubscription = await itemsAddedQuery.subscribe();
itemsAddedSubscription.on("create", onItemAdded);
}
/**************** Is the user logged in? ******************/
initUser = async () => {
let user = await Moralis.User.current();
if(user) {
/**************** User Is Logged In ******************/
$('#connectWalletHeaderBtn').hide();
$('#btnUserInfo').show();
$('#btnOpenCreateItem').show();
$('#btnOpenMyItems').show();
$('.header-user-mnu-wrap').show();
/**** Insert Username + Avatar to header ****/
let userName = user.get('username');
if(userName.length > 7) {
userName = userName.substring(0,7);
$('#headerUsername').text(`${userName}...`);
} else {
$('#headerUsername').text(userName);
}
const userAvatar = user.get('avatar');
if(userAvatar) {
$('#headerAvatar').attr("src", userAvatar.url());
} else {
$('.user-avatar-wrap').hide();
}
/**************** ClickEvents ******************/
// To User Info div
$('#btnUserInfo').on('click', function(e) {
e.preventDefault();
$('#marketplaceSection').hide();
$('#createItem').hide();
$('#userItems').hide();
$('#userInfoDiv').show("slow");
openUserInfo();
});
// To User Items div
$('#btnOpenMyItems').on('click', function(e) {
e.preventDefault();
$('#marketplaceSection').hide()
$('#userInfoDiv').hide();
$('#createItem').hide();
$('#userItems').show("slow");
});
// To Marketplace:
$('#btnCloseUserInfo').on('click', function(e) {
e.preventDefault();
$('#userInfoDiv').hide();
$('#createItem').hide();
$('#userItems').hide();
$('#marketplaceSection').show("slow");
});
$('#btnCloseCreateItem').on('click', function(e) {
e.preventDefault();
$('#userInfoDiv').hide();
$('#createItem').hide();
$('#userItems').hide();
$('#marketplaceSection').show("slow");
});
$('#btnCloseUserItems').on('click', function(e) {
e.preventDefault();
$('#userInfoDiv').hide();
$('#createItem').hide();
$('#userItems').hide();
$('#marketplaceSection').show("slow");
});
// To Main Page
$('.logo-link').on('click', function(e) {
e.preventDefault();
$('#userInfoDiv').hide();
$('#createItem').hide();
$('#userItems').hide();
$('#marketplaceSection').show("slow");
});
$('#explore').on('click', function(e) {
e.preventDefault();
$('#userInfoDiv').hide();
$('#createItem').hide();
$('#userItems').hide();
$('#marketplaceSection').show("slow");
});
$('#btnSaveUserInfo').on('click', function(e) {
e.preventDefault();
saveUserInfo();
});
/**************** Logout ******************/
$('#btnLogout').on('click', async function(e) {
e.preventDefault();
logout();
});
loadUserItems();
} else {
/**************** User Is NOT Logged In ******************/
$('#connectWalletHeaderBtn').show();
$('#btnUserInfo').hide();
$('#btnOpenCreateItem').hide();
$('#btnOpenMyItems').hide();
$('.header-user-mnu-wrap').hide();
$('#heroCreateBtn').on('click', function(e) {
e.preventDefault();
$('.modal').toggleClass('is-visible');
});
/**************** LOGIN ******************/
$('#btnConnect').on('click', async function(e) {
e.preventDefault();
login();
});
}
}
/**************** Login ******************/
login = async () => {
try {
let user = await Moralis.Web3.authenticate();
if(user) {
location.reload();
}
} catch (error) {
alert('Something went wrong, please try again...')
console.log(error)
}
}
/**************** Logout ******************/
logout = async () => {
await Moralis.User.logOut();
location.reload();
}
/**************** Load items ******************/
loadItems = async (price) => {
const items = await Moralis.Cloud.run("getItems");
user = await Moralis.User.current();
items.forEach(item => {
if(user) {
if(user.attributes.accounts.includes(item.ownerOf)) {
const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`);
if (userItemListing) userItemListing.parentNode.removeChild(userItemListing);
getAndRenderItemData(item, renderUserItem);
return;
};
}
getAndRenderItemData(item, renderItem, price);
});
}
/**************** Get Price For NFT Creation ******************/
passPriceToCreateItem = async (price) => {
user = await Moralis.User.current();
if(user) {
createItemEventListener(price);
}
}
// To Create Item div
createItemEventListener = (price) => {
// To Create an Item Menu Toolbar + HERO Section div
$('.btnCreateItem').on('click', function(e) {
e.preventDefault();
$('#marketplaceSection').hide()
$('#userInfoDiv').hide();
$('#userItems').hide();
$('#createItem').show("slow");
currencySum.innerText = '$0.00';
// ON KEYUP FUNC
$(createItemPriceField).keyup(function(e) {
currencySum.innerText = createItemPriceField.value * price;
switch(selectCurrency.value) {
case "0":
// alert(price);
// currencyId.innerHTML = "USD";
if(createItemPriceField.length != 0) {
let inUSD = (createItemPriceField.value * price);
currencySum.innerText = `${formatCurrency(inUSD)}`;
}
break;
case "1":
if(createItemPriceField.length != 0) {
let inETH = (createItemPriceField.value / price);
currencySum.innerText = inETH.toFixed(3);
}
break;
}
});
// ON CHANGE
$(selectCurrency).on('change', function(e) {
let dPrice = 0.0;
switch(selectCurrency.value) {
case "0":
currencyId.innerHTML = "USD";
createItemPriceField.value = null;
currencySum.innerText = `$${dPrice.toFixed(2)}`;
break;
case "1":
currencyId.innerHTML = "ETH";
createItemPriceField.value = null;
currencySum.innerText = `${dPrice.toFixed(3)}`;
break;
}
});
// Save Created Item
$('#btnCreateItem').on('click', function(e) {
e.preventDefault();
createItem(price);
});
});
}
/**************** Show user info div ******************/
openUserInfo = async () => {
user = await Moralis.User.current();
if (user){
$('#txtUsername').val(user.get('username'));
let userAvatar = await user.get('avatar');
if(userAvatar) {
$('.user-avatar-wrap').show();
let ava = document.getElementById('imgAvatar');
ava.src = userAvatar.url();
} else {
$('.user-avatar-wrap').hide();
$('.user_avatar_prev-wrap').hide();
}
$('#fileAvatar').on('change', function() {
$('.user_avatar_prev-wrap').show();
const preview = document.getElementById('imgAvatar');
const file = document.getElementById("fileAvatar").files[0];
const reader = new FileReader();
reader.addEventListener("load", function () {
// convert image file to base64 string
preview.src = reader.result;
}, false);
if (file) {
reader.readAsDataURL(file);
}
});
} else {
login();
}
}
/**************** Save user information ******************/
saveUserInfo = async () => {
let userUsernameField = document.getElementById('txtUsername');
user.set('username', userUsernameField.value);
let userAvatarFile = document.getElementById("fileAvatar");
if(userAvatarFile.files.length > 0) {
const avatar = new Moralis.File("avatar.jpg", userAvatarFile.files[0]);
user.set('avatar', avatar);
}
try {
await user.save();
alert("Details saved successfully!");
} catch (error) {
alert("Opps, something went wrong, please try again...");
console.log(error);
}
openUserInfo();
const userAvatar = user.get('avatar');
if(userAvatar) {
$('#headerAvatar').attr("src", userAvatar.url());
}
let userName = user.get('username');
if(userName.length > 7) {
userName = userName.substring(0,7);
$('#headerUsername').text(`${userName}...`);
} else {
$('#headerUsername').text(userName);
}
}
/**************** Load user items ******************/
loadUserItems = async () => {
const ownedItems = await Moralis.Cloud.run("getUserItems");
ownedItems.forEach( item => {
const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`);
if (userItemListing) return;
getAndRenderItemData(item, renderUserItem);
});
}
/**************** Render User Items Template ******************/
renderUserItem = (item) => {
const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`);
if (userItemListing) return;
const userItem = userItemTemplate.cloneNode(true);
userItem.id = `user-item-${item.tokenObjectId}`;
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;
const userItems = document.getElementById("userItemsList");
userItems.appendChild(userItem);
}
/**************** Create Item ******************/
createItem = async (price) => {
// Validation
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;
}
// Submiting the file path and file hash
const nftFile = new Moralis.File("nftFile.jpg", createItemFile.files[0]);
await nftFile.saveIPFS();
const nftFilePath = nftFile.ipfs();
const nftFileHash = nftFile.hash();
// creating an object with path & hash
const metadata = {
name: createItemNameField.value,
description: createItemDecriptionField.value,
image: nftFilePath
};
// Passing the object to save as ipfs
const nftFileMetadataFile = new Moralis.File("metadata.json", {base64 : btoa(JSON.stringify(metadata))});
await nftFileMetadataFile.saveIPFS();
// Getting the file path and file hash
const nftFileMetadataFilePath = nftFileMetadataFile.ipfs();
const nftFileMetadataFileHash = nftFileMetadataFile.hash();
const nftId = await mintNft(nftFileMetadataFilePath);
// Creating new class
const Item = Moralis.Object.extend("Item");
const item = new Item();
item.set('name', createItemNameField.value);
item.set('description', createItemDecriptionField.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();
user = await Moralis.User.current();
const userAddress = user.get('ethAddress');
switch(selectCurrency.value) {
case "0":
let sumWEI = await toWei(createItemPriceField.value);
await ensureMarketplaceIsApproved(nftId, TOKEN_CONTRACT_ADDRESS);
await marketplaceContract.methods.addItemToMarket(nftId, TOKEN_CONTRACT_ADDRESS, sumWEI).send({from: userAddress});
break;
case "1":
usdToWei = createItemPriceField.value / price;
usdToWei = usdToWei.toString();
let sumWEII = await toWei(usdToWei);
await ensureMarketplaceIsApproved(nftId, TOKEN_CONTRACT_ADDRESS);
await marketplaceContract.methods.addItemToMarket(nftId, TOKEN_CONTRACT_ADDRESS, sumWEII.toString()).send({from: userAddress});
break;
}
location.reload();
}
/**************** Mint NFT ******************/
mintNft = async (metadataUrl) => {
const receiept = await tokenContract.methods.createItem(metadataUrl).send({from: ethereum.selectedAddress});
return receiept.events.Transfer.returnValues.tokenId;
}
/**************** Ensure markeplace approved token ******************/
ensureMarketplaceIsApproved = async (tokenId, tokenAddress) => {
user = await Moralis.User.current();
const userAddress = user.get('ethAddress');
const web3 = await Moralis.enable();
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});
}
}
/**************** Buy Item ******************/
buyItem = async (item) => {
user = await Moralis.User.current();
if (!user){
login();
return;
}
await marketplaceContract.methods.buyItem(item.uid).send({from: user.get('ethAddress'), value: item.askingPrice});
}
/**************** Get item/s data ******************/
getAndRenderItemData = (item, renderFunction, price) => {
fetch(item.tokenUri)
.then(response => response.json())
.then(data => {
item.name = data.name;
item.description = data.description;
item.image = data.image;
renderFunction(item, price);
})
}
/**************** Render Marketplace Template ******************/
renderItem = async (item, price) => {
const itemForSale = marketplaceItemTemplate.cloneNode(true);
if(item.sellerAvatar) {
itemForSale.getElementsByTagName("img")[2].src = item.sellerAvatar.url();
itemForSale.getElementsByTagName("img")[2].alt = item.sellerUsername;
} else {
itemForSale.getElementsByTagName("img")[2].src = "../img/nft/preview/omaraqil.jpg";
itemForSale.getElementsByTagName("img")[2].alt = item.sellerUsername;
}
itemForSale.getElementsByTagName("img")[0].src = item.image;
itemForSale.getElementsByTagName("img")[0].alt = item.name;
itemForSale.getElementsByTagName("h5")[0].innerText = item.name;
itemForSale.getElementsByTagName("p")[0].innerText = item.description;
if(item.sellerUsername.length > 12) {
userName = item.sellerUsername.substring(0,12);
itemForSale.getElementsByTagName("div")[9].innerText = (`${userName}...`);
} else {
itemForSale.getElementsByTagName("div")[9].innerText = item.sellerUsername;
}
let toEthPrice = await toEth(item.askingPrice);
itemForSale.getElementsByTagName("span")[1].innerText = parseFloat(toEthPrice).toFixed(3);
itemForSale.getElementsByTagName("span")[2].innerText = toUSD(price, toEthPrice);
itemForSale.getElementsByTagName("button")[0].onclick = () => buyItem(item);
itemForSale.id = `item-${item.uid}`;
itemsForSale.appendChild(itemForSale);
}
/**************** Init Template ******************/
initTemplate = (id) => {
const template = document.getElementById(id);
template.id = "";
template.parentNode.removeChild(template);
return template;
}
/**************** When Item Sold Event ******************/
onItemSold = async (item) => {
const listing = document.getElementById(`item-${item.attributes.uid}`);
if (listing){
listing.parentNode.removeChild(listing);
}
user = await Moralis.User.current();
if (user){
const params = {uid: `${item.attributes.uid}`};
const soldItem = await Moralis.Cloud.run('getItem', params);
if (soldItem){
if (user.get('accounts').includes(item.attributes.buyer)){
getAndRenderItemData(soldItem, renderUserItem);
}
const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`);
if (userItemListing) userItemListing.parentNode.removeChild(userItemListing);
}
}
}
/**************** When Item Added For Sale ******************/
onItemAdded = async (item) => {
const params = {uid: `${item.attributes.uid}`};
const addedItem = await Moralis.Cloud.run('getItem', params);
if (addedItem){
user = await Moralis.User.current();
if (user){
if (user.get('accounts').includes(addedItem.ownerOf)){
const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`);
if (userItemListing) userItemListing.parentNode.removeChild(userItemListing);
getAndRenderItemData(addedItem, renderUserItem);
return;
}
}
getAndRenderItemData(addedItem, renderItem);
}
}
/**************** Helpers Functins ******************/
// Convert To WEI
toWei = async (sum) => {
const web3 = await Moralis.enable();
weiPrice = web3.utils.toWei(sum, "ether");
return weiPrice;
}
//Convert To ETH
toEth = async (sum) => {
const web3 = await Moralis.enable();
ethPrice = web3.utils.fromWei(sum, "ether");
return ethPrice;
}
// Get Current USD Price
getUSDPrice = async () => {
let ws = new WebSocket('wss://stream.binance.com:9443/ws/ethusdt@trade')
ws.onmessage = async (event) => {
let stockObject = JSON.parse(event.data)
price = await stockObject.p;
price = parseFloat(price).toFixed(2);
ws.close();
$('#itemsLoader').show();
setTimeout(async () => {
$('#itemsLoader').hide();
loadItems(price);
passPriceToCreateItem(price);
}, 100)
}
}
// Calculate to USD Price
toUSD = (price, item) => {
item = parseFloat(item);
let usd = item * price;
let usdCurrency = formatCurrency(usd);
return usdCurrency;
}
// Format To USD Currency
formatCurrency = (toFormat) => {
// Currency formator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_identification_and_negotiation)
var formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});
let usdOutput = formatter.format(toFormat); /* $2,500.00 */
return usdOutput;
}
const createItemFile = document.getElementById("fileCreateItemFile");
const createItemNameField = document.getElementById("txtCreateItemName");
const createItemDecriptionField = document.getElementById("txtCreateItemDescription");
const selectCurrency = document.getElementById("currencySelect");
const createItemPriceField = document.getElementById("numCreateItemPrice");
const itemsForSale = document.getElementById("itemsForSale");
const userItemTemplate = initTemplate("itemTemplate");
const marketplaceItemTemplate = initTemplate("marketplaceItemTemplate");
init();
CloudFunctions
Moralis.Cloud.define("getUserItems", async (request) => {
const query = new Moralis.Query("EthNFTOwners");
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({
"tokenObjectId": queryResults[i].id,
"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("EthNFTOwners");
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.Cloud.beforeSave("SoldItems", async (request) => {
const query = new Moralis.Query("ItemsForSale");
query.equalTo("uid", request.object.get('uid'));
const item = await query.first();
if(item) {
request.object.set('item', item);
item.set('isSold', true);
// await item.save();
const userQuery = new Moralis.Query(Moralis.User);
userQuery.equalTo("accounts", request.object.get('buyer'));
const userObject = await userQuery.first({useMasterKey:true});
if(userObject) {
request.object.set('user', userObject);
}
}
});
Moralis.Cloud.define("getItems", async (request) => {
const query = new Moralis.Query("ItemsForSale");
query.notEqualTo("isSold", true);
query.select("uid", "tokenAddress", "tokenId", "askingPrice", "token.token_uri", "token.symbol", "token.owner_of", "token.id", "user.username", "user.avatar");
const queryResults = await query.find({useMasterKey:true});
const results = [];
for (let i = 0; i < queryResults.length; ++i) {
if(!queryResults[i].attributes.token || !queryResults[i].attributes.user) continue;
results.push({
"uid": queryResults[i].attributes.uid,
"tokenId": queryResults[i].attributes.token_id,
"tokenAddress": queryResults[i].attributes.token_address,
"askingPrice": queryResults[i].attributes.askingPrice,
"symbol": queryResults[i].attributes.token.attributes.symbol,
"tokenUri": queryResults[i].attributes.token.attributes.token_uri,
"ownerOf": queryResults[i].attributes.token.attributes.owner_of,
"tokenObjectId": queryResults[i].attributes.token.id,
"sellerUsername": queryResults[i].attributes.user.attributes.username,
"sellerAvatar": queryResults[i].attributes.user.attributes.avatar,
});
}
return results;
});
Moralis.Cloud.define("getItem", async (request) => {
const query = new Moralis.Query("ItemsForSale");
query.notEqualTo("uid", request.params.uid);
query.select("uid", "tokenAddress", "tokenId", "askingPrice", "token.token_uri", "token.symbol", "token.owner_of", "tokenId", "user.username", "user.avatar");
const queryResult = await query.first({useMasterKey:true});
if(!queryResult) return;
return {
"uid": queryResult.attributes.uid,
"tokenId": queryResult.attributes.token_id,
"tokenAddress": queryResult.attributes.token_address,
"askingPrice": queryResult.attributes.askingPrice,
"symbol": queryResult.attributes.token.attributes.symbol,
"tokenUri": queryResult.attributes.token.attributes.token_uri,
"ownerOf": queryResult.attributes.token.attributes.owner_of,
"tokenObjectId": queryResult.attributes.token.id,
"sellerUsername": queryResult.attributes.user.attributes.username,
"sellerAvatar": queryResult.attributes.user.attributes.avatar,
};
});
sync contracts event is also in place checked 435345345 times.