index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Morarable</title>
</head>
<body>
<div>
<button id="btnConnect">Connect Wallet</button>
<button id="btnUserInfo">Profile</button>
<button id="btnOpenCreateItem">Create</button>
<button id="btnMyItems">My items</button>
</div>
<div id="userInfo">
<h4>User Profile</h4>
<input type ="text" id = "txtUsername" required placeholder= "Enter username">
<input type ="text" id = "txtEmail" required placeholder= "Enter email">
<small>Optional</small>
<img width="50" height="50" src="" id="imgAvatar" alt="">
<label for="fileAvatar">Select Avatar</label>
<input type="file" id="fileAvatar">
<button id="btnLogout">Log out</button>
<button id="btnCloseUserInfo">Close</button>
<button id="btnSaveUserInfo">Save</button>
</div>
<div id="createItem">
<h4>Create Item</h4>
<input type ="text" id = "txtCreateItemName" required placeholder= "Enter name">
<textarea id = "txtCreateItemDescription" cols="30" rows="5" placeholder= "Enter description"></textarea>
<input type="number" min="1" step="1" id="numberCreateItemPrice" placeholder="Enter price" required>
<label for="selectCreateItemStatus">Status</label>
<select id="selectCreateItemStatus">
<option value="0">Not for sale</option>
<option value="1">Instant buy</option>
<option value="2">Accept Offers</option>
</select>
<label for="fileCreateItemFile">Select File</label>
<input type="file" id="fileCreateItemFile">
<button id="btnCloseCreateItem">Close</button>
<button id="btnCreateItem">Create!</button>
</div>
<div id="userItems">
<h4>My Items</h4>
<div id="userItemsList"></div>
<button id="btnCloseUserItems">Close</button>
</div>
<div id="itemsForSale"></div>
<div id="itemTemplate">
<img src="" alt="">
<h5></h5>
<p></p>
</div>
<div id="marketplaceItemTemplate">
<img src="" alt="">
<h6></h6>
<img src="" alt="">
<h5></h5>
<p></p>
<button></button>
</div>
<script src="https://cdn.jsdelivr.net/npm/web3@latest/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>
</body>
</html>
main.js
Moralis.initialize("748DE3i3o1yJPE6S41YRRXGfo34wQrhq8jziOkd6");
Moralis.serverURL = "https://f2opvlqwefqt.usemoralis.com:2053/server";
const TOKEN_CONTRACT_ADDRESS = "0xa12084e15E28F44D55B41C443A829004973733EE";
const MARKETPLACE_CONTRACT_ADDRESS =
"0xe8219c8c928943e580779C2dBAbED2B5E732440e";
init = async () => {
hideElement(openUserItemsButton);
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();
const soldItemsQuery = new Moralis.Query('SoldItems');
const soldItemsSubscription = await soldItemsQuery.subscribe();
soldItemsSubscription.on("create", onItemSold);
const itemsAddedQuery = new Moralis.Query('ItemsForSale');
const itemsAddedSubscription = await soldItemsQuery.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){
if(user.get('accounts').includes(item.attributes.buyer)){
const params = {uid: `${item.attributes.uid}`};
const soldItem = await Moralis.Cloud.run('getItem', params);
if(soldItem){
getAndRenderItemData(soldItem, renderUserItem);
}
};
}
}
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)){
getAndRenderItemData(addedItem, renderUserItem);
return;
}
};
getAndRenderItemData(addedItem, renderItem);
}
}
initUser = async () => {
if (await Moralis.User.current()) {
showElement(userProfileButton);
showElement(openCreateItemButton);
showElement(openUserItemsButton);
hideElement(userConnectButton);
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);
}
showElement(userInfo);
} else {
login();
}
};
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);
}
console.log("Saving...");
await user.save();
console.log("Saved!");
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 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);
// Simple syntax to create a new subclass of Moralis.Object.
const Item = Moralis.Object.extend("Item");
// Create a new instance of that class.
const item = new Item();
item.set("name", createItemNameField.value);
item.set("description", createItemDescriptionField.value);
item.set("nftFilePath", nftFilePath.value);
item.set("nftFileHash", nftFileHash.value);
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;
};
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");
user = await Moralis.User.current();
items.forEach((item) => {
if(user){
if(user.attributes.accounts.includes(item.ownerOf)) return;
}
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);
console.log("Are we here");
if(item.avatar){
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 = () => {
showElement(createItemForm);
};
// 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);
};
const userLogout = document.getElementById("btnLogout");
userLogout.onclick = logout;
const userSaveInfo = document.getElementById("btnSaveUserInfo");
userSaveInfo.onclick = saveUserInfo;
//Item Creation
const createItemForm = document.getElementById("createItem");
const createItemNameField = document.getElementById("txtCreateItemName");
const createItemDescriptionField = document.getElementById(
"txtCreateItemDescription"
);
const createItemPriceField = document.getElementById("numberCreateItemPrice");
const createItemStatusField = document.getElementById("selectCreateItemStatus");
const createItemFile = document.getElementById("fileCreateItemFile");
document.getElementById("btnCloseCreateItem").onclick = () => {
hideElement(createItemForm);
};
const createItemButton = document.getElementById("btnCreateItem");
createItemButton.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();
abi.js
var tokenContractAbi = [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "Items",
"outputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "address",
"name": "creator",
"type": "address"
},
{
"internalType": "string",
"name": "uri",
"type": "string"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "uri",
"type": "string"
}
],
"name": "createItem",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
}
];
var marketplaceContractAbi = [
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "askingPrice",
"type": "uint256"
}
],
"name": "itemAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "buyer",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "askingPrice",
"type": "uint256"
}
],
"name": "itemSold",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "itemsForSale",
"outputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address payable",
"name": "seller",
"type": "address"
},
{
"internalType": "uint256",
"name": "askingPrice",
"type": "uint256"
},
{
"internalType": "bool",
"name": "isSold",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "askingPrice",
"type": "uint256"
}
],
"name": "addItemToMarket",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "buyItem",
"outputs": [],
"stateMutability": "payable",
"type": "function",
"payable": true
}
];
MorarableToken.sol
pragma solidity ^0.8.0;
import "../node_modules/@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "../node_modules/@openzeppelin/contracts/utils/Counters.sol";
contract MorarableToken is ERC721 {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() ERC721("MorarableToken", "MORA") {}
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;
}
}
Marketplace.sol
pragma solidity ^0.8.0;
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 item"
);
_;
}
modifier IsForSale(uint256 id) {
require(itemsForSale[id].isSold == false, "Item is already sold!");
_;
}
function addItemToMarket(
uint256 tokenId,
address tokenAddress,
uint256 askingPrice
)
external
OnlyItemOwner(tokenAddress, tokenId)
HasTransferApproval(tokenAddress, tokenId)
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)
external
payable
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);
}
}
When I click to buy the item I want I get the error as shown. I have tried re-pasting the .json compiled code to abi.js files again but issue still remains.