// contracts/NFTMarketplace.sol // SPDX-License-Identifier: MIT OR Apache-2.0 // // adapt and edit from (Nader Dabit): // https://github.com/dabit3/polygon-ethereum-nextjs-marketplace/blob/main/contracts/Market.sol pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import 'abdk-libraries-solidity/ABDKMathQuad.sol'; import "@openzeppelin/contracts/access/Ownable.sol"; import "./TixSellEventLibrary.sol"; import "../interfaces/ITicketContract.sol"; import "../interfaces/ITicketTypeContract.sol"; import "../interfaces/IEventContract.sol"; import "../interfaces/ITicketTypeContract.sol"; contract NFTMarketplace is Ownable,ReentrancyGuard,AccessControl { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); uint256 private _itemCounter;//start from 1 uint256 private _itemSoldCounter; enum State { Created, Release, Inactive } struct MarketItem { uint id; address nftContract; uint256 tokenId; address payable seller; address payable buyer; uint256 price; State state; } mapping(uint256 => MarketItem) private marketItems; address tixSellpaymentSplitter; event MarketItemCreated ( uint indexed id, address indexed nftContract, uint256 indexed tokenId, address seller, address buyer, uint256 price, State state ); event MarketItemSold ( uint indexed id, address indexed nftContract, uint256 indexed tokenId, address seller, address buyer, uint256 price, State state ); constructor(address initialOwner, address[] memory _admins, address _tixSellpaymentSplitter) Ownable(initialOwner) { for (uint256 i = 0; i < _admins.length; ++i) { _grantRole(ADMIN_ROLE, _admins[i]); _grantRole(DEFAULT_ADMIN_ROLE, _admins[i]); } tixSellpaymentSplitter = _tixSellpaymentSplitter; } modifier onlyFounders() { require( hasRole(ADMIN_ROLE, msg.sender), "Only founders can do that" ); _; } modifier onlyAdmin() { require(msg.sender == owner() || hasRole(ADMIN_ROLE, msg.sender), "Only admins can do that"); _; } /** * @dev create a MarketItem for NFT sale on the marketplace. * * List an NFT. */ function createMarketItem( address nftContract, uint256 tokenId, uint256 price ) public payable nonReentrant { TixSellLibrary.TicketType memory theTicketType = getTicketType(nftContract,tokenId); require(theTicketType.sellable,"not sellable"); require(price > 0, "Price must be at least 1 wei"); require(price <= theTicketType.maxSellablePrice,"Sell price too high"); require(IERC721(nftContract).getApproved(tokenId) == address(this), "NFT must be approved to market"); // change to approve mechanism from the original direct transfer to market IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId); _itemCounter+=1; uint256 id = _itemCounter; marketItems[id] = MarketItem( id, nftContract, tokenId, payable(msg.sender), payable(address(0)), price, State.Created ); emit MarketItemCreated( id, nftContract, tokenId, msg.sender, address(0), price, State.Created ); } /** * @dev delete a MarketItem from the marketplace. * * de-List an NFT. * * todo ERC721.approve can't work properly!! comment out */ function deleteMarketItem(uint256 itemId) public nonReentrant { require(itemId <= _itemCounter, "id must <= item count"); require(marketItems[itemId].state == State.Created, "item must be on market"); MarketItem storage item = marketItems[itemId]; require(IERC721(item.nftContract).ownerOf(item.tokenId) == msg.sender, "must be the owner"); require(IERC721(item.nftContract).getApproved(item.tokenId) == address(this), "NFT must be approved to market"); item.state = State.Inactive; emit MarketItemSold( itemId, item.nftContract, item.tokenId, item.seller, address(0), 0, State.Inactive ); } /** * @dev (buyer) buy a MarketItem from the marketplace. * Transfers ownership of the item, as well as funds * NFT: seller -> buyer * value: buyer -> seller * */ function createMarketSale( address nftContract, uint256 id ) public payable nonReentrant { MarketItem storage item = marketItems[id]; //should use storge!!!! uint price = item.price; uint tokenId = item.tokenId; require(item.state == State.Created || item.state==State.Inactive,"Not for sell"); require(msg.value == price, "Please submit the asking price"); require(IERC721(nftContract).getApproved(tokenId) == address(this), "NFT must be approved to market"); IERC721(nftContract).transferFrom(item.seller, msg.sender, tokenId); // calculate royalties for Organizer // calculate fees for SellTix // amount for seller is msg.value - feesOrganizer - feesSellTix TixSellLibrary.TicketType memory theTicketType = getTicketType(nftContract,tokenId); uint256 royaltySellable = theTicketType.royaltySellable; uint256 feesOrganizer = mulDiv(royaltySellable, msg.value, 100); uint256 amountForSeller = msg.value - feesOrganizer ; uint256 feesSellTix = mulDiv(2, msg.value, 100); //2% fees amountForSeller -= feesSellTix; payable(tixSellpaymentSplitter).transfer(feesSellTix); // Get the orga payment splitter address resellPaiementSplitter = ITicketContract(nftContract).getResellPaymentSplitter(); payable(resellPaiementSplitter).transfer(feesOrganizer); item.seller.transfer(amountForSeller); item.buyer = payable(msg.sender); item.state = State.Release; _itemSoldCounter+=1; emit MarketItemSold( id, nftContract, tokenId, item.seller, msg.sender, price, State.Release ); } /** * @dev Returns all unsold market items * condition: * 1) state == Created * 2) buyer = 0x0 * 3) still have approve */ function fetchActiveItems() public view returns (MarketItem[] memory) { return fetchHepler(FetchOperator.ActiveItems); } /** * @dev Returns only market items a user has purchased * todo pagination */ function fetchMyPurchasedItems() public view returns (MarketItem[] memory) { return fetchHepler(FetchOperator.MyPurchasedItems); } /** * @dev Returns only market items a user has created * todo pagination */ function fetchMyCreatedItems() public view returns (MarketItem[] memory) { return fetchHepler(FetchOperator.MyCreatedItems); } enum FetchOperator { ActiveItems, MyPurchasedItems, MyCreatedItems} /** * @dev fetch helper * todo pagination */ function fetchHepler(FetchOperator _op) private view returns (MarketItem[] memory) { uint total = _itemCounter; uint itemCount = 0; for (uint i = 1; i <= total; i++) { if (isCondition(marketItems[i], _op)) { itemCount ++; } } uint index = 0; MarketItem[] memory items = new MarketItem[](itemCount); for (uint i = 1; i <= total; i++) { if (isCondition(marketItems[i], _op)) { items[index] = marketItems[i]; index ++; } } return items; } /** * @dev helper to build condition * * todo should reduce duplicate contract call here * (IERC721(item.nftContract).getApproved(item.tokenId) called in two loop */ function isCondition(MarketItem memory item, FetchOperator _op) private view returns (bool){ if(_op == FetchOperator.MyCreatedItems){ return (item.seller == msg.sender && item.state != State.Inactive )? true : false; }else if(_op == FetchOperator.MyPurchasedItems){ return (item.buyer == msg.sender) ? true: false; }else if(_op == FetchOperator.ActiveItems){ return (item.buyer == address(0) && item.state == State.Created && (IERC721(item.nftContract).getApproved(item.tokenId) == address(this)) )? true : false; }else{ return false; } } function getTicketType(address nftContract,uint256 tokenId) private view returns (TixSellLibrary.TicketType memory){ uint256 theTicketTypeId = ITicketContract(nftContract).getTicketTypesForTicket(tokenId); address eventContract = ITicketContract(nftContract).getEventContract(); address ticketTypeContract = IEventContract(eventContract).getTicketTypeContract(); TixSellLibrary.TicketType memory theTicketType = ITicketTypeContract(ticketTypeContract).getTicketTypeInfo(theTicketTypeId); return theTicketType; } function mulDiv (uint x, uint y, uint z) public pure returns (uint) { return ABDKMathQuad.toUInt ( ABDKMathQuad.div ( ABDKMathQuad.mul ( ABDKMathQuad.fromUInt (x), ABDKMathQuad.fromUInt (y) ), ABDKMathQuad.fromUInt (z) ) ); } }