// SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; interface IERC721Receiver { //cannot use the one from openzeppelin as we need payable function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) payable external returns (bytes4); } contract Market { //could be done with a struct mapping(address => mapping(uint256 => mapping(uint256 => uint256))) private _offers; mapping(address => mapping(uint256 => mapping(address => uint256))) private _offerMap; mapping(address => mapping(uint256 => uint256)) private _totalOfferCount; event Offer(address nftContract, uint256 nftId, address offerer, uint256 value); event Withdraw(address nftContract, uint256 nftId, address offerer, uint256 value); event Accepted(address nftContract, uint256 nftId, address from, address to, uint256 value); function makeOffer(address nftContract, uint256 nftId) payable external { require(_offerMap[nftContract][nftId][msg.sender] == 0, "already made an offer"); uint256 index = totalOffers(nftContract, nftId) + 1; _offers[nftContract][nftId][index] = msg.value; _offerMap[nftContract][nftId][msg.sender] = index; _totalOfferCount[nftContract][nftId] = index; emit Offer(nftContract, nftId, msg.sender, msg.value); } function totalOffers(address nftContract, uint256 nftId) view public returns (uint256){ return _totalOfferCount[nftContract][nftId]; } function offerIndex(address nftContract, uint256 nftId, address offerer) public view returns (uint256) { return _offerMap[nftContract][nftId][offerer]; } function withdrawOffer(address nftContract, uint256 nftId) payable external { uint256 index = _offerMap[nftContract][nftId][msg.sender]; require(index != 0, "no offers made"); uint256 val = _offers[nftContract][nftId][index]; require(val != 0, "no offers made"); removeAndShift(nftContract, nftId, index); delete(_offerMap[nftContract][nftId][msg.sender]); payable(msg.sender).transfer(val); emit Withdraw(nftContract, nftId, msg.sender, val); } function offers(address nftContract, uint256 nftId, uint256 index) external view returns (uint256) { return _offers[nftContract][nftId][index]; } function onERC721Received(address, address from, uint256 nftId, bytes calldata data) payable external returns (bytes4) { address offerer = bytesToAddress(data); uint256 index = offerIndex(msg.sender, nftId, offerer); require(index > 0, "offer invalid"); uint256 val = _offers[msg.sender][nftId][index]; require(val > 0, "offer invalid"); removeAndShift(msg.sender, nftId, index); delete(_offerMap[msg.sender][nftId][from]); payable(from).transfer(val); IERC721(msg.sender).transferFrom(address(this), offerer, nftId); emit Accepted(msg.sender, nftId, from, offerer, val); return IERC721Receiver.onERC721Received.selector; } function removeAndShift(address nftContract, uint256 nftId, uint256 index) internal { delete(_offers[nftContract][nftId][index]); uint256 currentTotalOffers = totalOffers(nftContract, nftId); _offers[nftContract][nftId][index] = _offers[nftContract][nftId][currentTotalOffers]; delete(_offers[nftContract][nftId][currentTotalOffers]); _totalOfferCount[nftContract][nftId] = currentTotalOffers - 1; } function bytesToAddress(bytes memory bs) private pure returns (address) { require(bs.length >= 20, "slicing out of range"); address addr; assembly { addr := mload(add(bs,20)) } return addr; } }