Hi Folks,
Iâve worked through the tute, cloned the code from git but continue to have 2x errors that I havenât been able to solve. Appreciate any tips/suggesstions.
I believe my cloud_functions are accurate, I made the change to EthNFTOwners
Error from console -
main.js:182 Uncaught (in promise) ReferenceError: tokenContract is not defined
at mintNft (main.js:182:21)
at HTMLButtonElement.createItem (main.js:163:25)
main.js
/* Moralis init code */
const serverUrl = âhttps://ct6uqqk9xhwm.usemoralis.com:2053/serverâ;
const appId = âEAW2GL3JFdQE8eZGVTF4pZofH6kO4LukuJDh4CKsâ;
const TOKEN_CONTRACT_ADDRESS = â0x5725e2635606a8B956db0C7F563c92178cFD0D0Fâ;
const MARKETPLACE_CONTRACT_ADDRESS = â0x406DE3e82BB1758574E3639ec30952aC60a79782â;
Moralis.start({ serverUrl, appId });
init = async () => {
hideElement(userItemsSection); 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(); 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);
}
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); } }
}
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); }
}
initUser = async () => {
if (await Moralis.User.current()){ hideElement(userConnectButton); showElement(userProfileButton); showElement(openCreateItemButton); showElement(openUserItemsButton); loadUserItems(); }else{ showElement(userConnectButton); hideElement(userProfileButton); hideElement(openCreateItemButton); hideElement(openUserItemsButton); }
}
login = async () => {
try { await Moralis.Web3.authenticate(); initUser(); } catch (error) { alert(error) }
}
logout = async () => {
await Moralis.User.logOut(); hideElement(userInfo); initUser();
}
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); } $('#userInfo').modal('show'); }else{ login(); }
}
saveUserInfo = async () => {
user.set('email', userEmailField.value); user.set('username', userUsernameField.value); if (userAvatarFile.files.length > 0) { const avatar = new Moralis.File("avatar1.jpg", userAvatarFile.files[0]); user.set('avatar', avatar); } await user.save(); alert("User info saved successfully!"); openUserInfo();
}
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 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 nftId = await mintNft(nftFileMetadataFilePath); 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;
}
openUserItems = async () => {
user = await Moralis.User.current(); if (user){ $('#userItems').modal('show'); }else{ login(); }
}
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); });
}
loadItems = async () => {
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); });
}
initTemplate = (id) => {
const template = document.getElementById(id); template.id = ""; template.parentNode.removeChild(template); return template;
}
renderUserItem = async (item) => {
const userItemListing = document.getElementById(`user-item-${item.tokenObjectId}`); if (userItemListing) return; 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; userItem.getElementsByTagName("input")[0].value = item.askingPrice ?? 1; userItem.getElementsByTagName("input")[0].disabled = item.askingPrice > 0; userItem.getElementsByTagName("button")[0].disabled = item.askingPrice > 0; userItem.getElementsByTagName("button")[0].onclick = async () => { user = await Moralis.User.current(); if (!user){ login(); return; } await ensureMarketplaceIsApproved(item.tokenId, item.tokenAddress); await marketplaceContract.methods.addItemToMarket(item.tokenId, item.tokenAddress, userItem.getElementsByTagName("input")[0].value).send({from: user.get('ethAddress') }); }; userItem.id = `user-item-${item.tokenObjectId}` 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.getElementsByTagName("button")[0].onclick = () => buyItem(item); itemForSale.id = `item-${item.uid}`; itemsForSale.appendChild(itemForSale);
}
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}); }
}
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});
}
hideElement = (element) => element.style.display = ânoneâ;
showElement = (element) => element.style.display = âblockâ;
// Navbar
const userConnectButton = document.getElementById(âbtnConnectâ);
userConnectButton.onclick = login;
const userProfileButton = document.getElementById(âbtnUserInfoâ);
userProfileButton.onclick = openUserInfo;
const openCreateItemButton = document.getElementById(âbtnOpenCreateItemâ);
openCreateItemButton.onclick = () => $(â#createItemâ).modal(âshowâ);
// User profile
const userInfo = document.getElementById(âuserInfoâ);
const userUsernameField = 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(âbtnLogoutâ).onclick = logout;
document.getElementById(âbtnSaveUserInfoâ).onclick = saveUserInfo;
// Item creation
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â);
document.getElementById(âbtnCloseCreateItemâ).onclick = () => hideElement(createItemForm);
document.getElementById(âbtnCreateItemâ).onclick = createItem;
// User items
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â);
const marketplaceItemTemplate = initTemplate(âmarketplaceItemTemplateâ);
// Items for sale
const itemsForSale = document.getElementById(âitemsForSaleâ);
init();
index.html
<meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous"> <link rel="stylesheet" href="main.css"> <title>SITE NAME</title>
<nav class="navbar navbar-expand-lg navbar-dark bg-transparent border-bottom border-light"> <a class="navbar-brand btn btn-outline-moralis" href="#">SITE</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <form class="mx-2 d-inline flex-md-fill"> <input type="search" class="bg-transparent border-light form-control text-light mr-sm-2 rounded-pill" placeholder="Search by item or creator"> </form> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" id="btnMyItems" href="#">My items</a> </li> </ul> <div class="d-flex"> <button class="btn btn-primary rounded-pill my-2 my-sm-0 mr-md-2" id="btnOpenCreateItem">Create</button> <button class="btn btn-outline-moralis rounded-pill my-2 my-sm-0 " id="btnConnect">Connect wallet</button> <button class="btn btn-outline-moralis rounded-pill my-2 my-sm-0" id="btnUserInfo">Profile</button> </div> </div> </nav> <div class="container"> <div class="row row-cols-1 row-cols-md-4 mt-5" id="itemsForSale"> </div> </div> <div class="col mb-4" id="marketplaceItemTemplate"> <div class="card h-100 border-light bg-transparent text-light"> <nav class="card-header navbar navbar-dark text-light p-1"> <img width="30" height="30" class="d-inline-block align-top rounded-circle" src="" alt=""> <span></span> </nav> <img src="..." class="card-img-top" alt="..."> <div class="card-body d-flex align-items-end"> <div class="w-100"> <h5 class="card-title"></h5> <p class="card-text"></p> <button class="btn btn-primary btn-block"></button> </div> </div> </div> </div> <div class="col mb-4" id="itemTemplate"> <div class="card h-100 border-light bg-transparent text-light"> <img src="..." class="card-img-top" alt="..."> <div class="card-body d-flex align-items-end"> <div class="w-100"> <h5 class="card-title"></h5> <p class="card-text"></p> <div class="input-group mb-3"> <input type="number" min="1" step="1" class="form-control" placeholder="Price"> <div class="input-group-append"> <button class="btn btn-outline-secondary" type="button">Put for sale</button> </div> </div> </div> </div> </div> </div> <!-- Modal --> <div class="modal fade" id="userInfo" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content bg-dark text-light"> <div class="modal-header"> <h5 class="modal-title" >User Profile</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div class="form-group"> <label for="txtUsername">Username</label> <input type="text" class="form-control" id="txtUsername" required placeholder="Enter username"> </div> <div class="form-group"> <label for="txtEmail">Email address</label> <input type="email" class="form-control" id="txtEmail" aria-describedby="emailHelp" placeholder="Enter email"> <small id="emailHelp" class="form-text text-muted">Optional</small> </div> <img width="50" height="50" src="" id="imgAvatar" alt=""> <div class="form-group"> <label for="fileAvatar">Select Avatar</label> <input type="file" class="form-control-file" id="fileAvatar"> </div> </div> <div class="modal-footer"> <button type="button" id="btnLogout" class="btn btn-secondary" data-dismiss="modal">Log out</button> <button type="button" id="btnCloseUserInfo" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" id="btnSaveUserInfo" class="btn btn-primary">Save</button> </div> </div> </div> </div> <!-- Modal --> <div class="modal fade" id="createItem" tabindex="-1"> <div class="modal-dialog"> <div class="modal-content bg-dark text-light"> <div class="modal-header"> <h5 class="modal-title" >Create Item</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div class="form-group"> <label for="txtUsername">Name</label> <input type="text" class="form-control" id="txtCreateItemName" required placeholder="Enter name"> </div> <div class="form-group"> <label for="txtCreateItemDescription">Description</label> <textarea class="form-control" id="txtCreateItemDescription" cols="30" rows="5" placeholder="Enter description"></textarea> </div> <div class="form-group"> <label for="txtUsername">Price</label> <input type="number" min="1" step="1" id="numCreateItemPrice" placeholder="Enter price" required class="form-control"> </div> <div class="form-group"> <label for="selectCreateItemStatus">Status</label> <select class="form-control" id="selectCreateItemStatus"> <option value="0">Not for sale</option> <option value="1">Instant buy</option> <option value="2">Accept Offers</option> </select> </div> <div class="form-group"> <label for="fileCreateItemFile">Select file</label> <input type="file" class="form-control-file" id="fileCreateItemFile"> </div> </div> <div class="modal-footer"> <button type="button" id="btnCloseCreateItem" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" id="btnCreateItem" class="btn btn-primary">Create!</button> </div> </div> </div> </div> <!-- Modal --> <div class="modal fade" id="userItems" tabindex="-1"> <div class="modal-dialog modal-xl"> <div class="modal-content bg-dark text-light"> <div class="modal-header"> <h5 class="modal-title" >My Items</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body row row-cols-1 row-cols-md-4 mt-5" id="userItemsList"> </div> <div class="modal-footer"> <button type="button" id="btnCloseUserItems" class="btn btn-secondary" data-dismiss="modal">Close</button> </div> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/web3.min.js"></script> <script src="https://unpkg.com/moralis/dist/moralis.js"></script> <script src="abi.js"></script> <script src="main.js"></script>
Token.sol
pragma solidity ^0.8.0;
import ââŚ/node_modules/@openzeppelin/contracts/token/ERC721/ERC721.solâ;
import ââŚ/node_modules/@openzeppelin/contracts/utils/Counters.solâ;
contract ReReToken is ERC721 {
using Counters for Counters.Counter; Counters.Counter private _tokenIds; constructor () ERC721("ReReToken", "RERE"){} struct Item { uint256 id; address creator; string uri; } mapping (uint256 => Item) public Items; function createItem(string memory uri) public returns (uint256){ _tokenIds.increment(); uint256 newItemId = _tokenIds.current(); _safeMint(msg.sender, newItemId); Items[newItemId] = Item(newItemId, msg.sender, uri); return newItemId; } function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); return Items[tokenId].uri; }
}
Thanks!!!