// SPDX-License-Identifier: MIT pragma solidity ^0.8.6; import './Collectible.sol'; contract Marketplace is Collectible { using SafeMath for uint256; struct Listing { uint256 price; address owner; } // Mapping for a token id to Listing mapping (uint256 => Listing) public tokenIdToListing; // Mapping to prevent the same item being listed twice mapping (uint256 => bool) public hasBeenListed; // Mapping used for listing when the owner transfers the token to the contract and would then wish to cancel the listing mapping (uint256 => address) public claimableByAccount; /** * @dev Emitted when a `tokenId` has been listed for a `price` by a `seller` */ event ItemListed(uint256 tokenId, uint256 price, address seller); /** * @dev Emitted when a `tokenId` listing for a `price` has been cancelled by a `seller` */ event ListingCancelled(uint256 tokenId, uint256 price, address seller); /** * @dev Emitted when a `tokenId` has been bought for a `price` by a `buyer` */ event ItemBought(uint256 tokenId, uint256 price, address buyer); modifier onlyTokenOwner(uint256 tokenId) { require( msg.sender == ownerOf(tokenId), "Only the owner of the token id can call this function." ); _; } modifier onlyListingAccount(uint256 tokenId) { require( msg.sender == claimableByAccount[tokenId], "Only the address that has listed the token can cancel the listing." ); _; } /** * @dev list an item with a `tokenId` for a `price` * * Requirements: * - Only the owner of the `tokenId` can list the item * - The `tokenId` can only be listed once * * Emits a {Transfer} event - transfer the token to this smart contract. * Emits a {ItemListed} event */ function listItem(uint256 tokenId, uint256 price) public onlyTokenOwner(tokenId) { require(!hasBeenListed[tokenId], "The token can only be listed once."); //send the token to the smart contract _transfer(msg.sender, address(this), tokenId); claimableByAccount[tokenId] = msg.sender; tokenIdToListing[tokenId] = Listing( price, msg.sender ); hasBeenListed[tokenId] = true; emit ItemListed(tokenId, price, msg.sender); } /** * @dev Cancel a listing of an item with a `tokenId` * * Requirements: * - Only the account that has listed the `tokenId` can delist it * * Emits a {Transfer} event - transfer the token from this smart contract to the owner. * Emits a {ListingCancelled} event. */ function cancelListing(uint256 tokenId) public onlyListingAccount(tokenId) { //send the token from the smart contract back to the one who listed it _transfer(address(this), msg.sender, tokenId); uint256 price = tokenIdToListing[tokenId].price; delete claimableByAccount[tokenId]; delete tokenIdToListing[tokenId]; delete hasBeenListed[tokenId]; emit ListingCancelled(tokenId, price, msg.sender); } /** * @dev Buy an item with a `tokenId` and pay the owner and the creator * * Requirements: * - `tokenId` has to be listed * - `price` needs to be the same as the value sent by the caller * * Emits a {Transfer} event - transfer the item from this smart contract to the buyer. * Emits an {ItemBought} event. */ function buyItem(uint256 tokenId) public payable { require(hasBeenListed[tokenId], "The token needs to be listed in order to be bought."); require(tokenIdToListing[tokenId].price == msg.value, "You need to pay the correct price."); //split up the price between owner and creator uint256 royaltyForCreator = tokenIdToItem[tokenId].royalty.mul(msg.value).div(100); uint256 remainder = msg.value.sub(royaltyForCreator); //send to creator (bool isRoyaltySent, ) = tokenIdToItem[tokenId].creator.call{value: royaltyForCreator}(""); require(isRoyaltySent, "Failed to send AVAX"); //send to owner (bool isRemainderSent, ) = tokenIdToItem[tokenId].owner.call{value: remainder}(""); require(isRemainderSent, "Failed to send AVAX"); //transfer the token from the smart contract back to the buyer _transfer(address(this), msg.sender, tokenId); //Modify the owner property of the item to be the buyer Item storage item = tokenIdToItem[tokenId]; item.owner = msg.sender; //clean up delete tokenIdToListing[tokenId]; delete claimableByAccount[tokenId]; delete hasBeenListed[tokenId]; emit ItemBought(tokenId, msg.value, msg.sender); } /** * @dev return a listing of a `tokenId` */ function getListing(uint256 tokenId) public view returns (uint256, address) { return (tokenIdToListing[tokenId].price, tokenIdToListing[tokenId].owner); } }