// SPDX-License-Identifier: MIT License
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract CryptoPhunksMarket {
IERC721 phunksContract; // instance of the CryptoPhunks contract
address contractOwner; // owner can change phunksContract
struct Offer {
bool isForSale;
uint phunkIndex;
address seller;
uint minValue; // in ether
address onlySellTo; // specify to sell only to a specific person
}
struct Bid {
bool hasBid;
uint phunkIndex;
address bidder;
uint value;
}
// A record of phunks that are offered for sale at a specific minimum value, and perhaps to a specific person
mapping (uint => Offer) public phunksOfferedForSale;
// A record of the highest phunk bid
mapping (uint => Bid) public phunkBids;
// A record of pending ETH withdrawls by address
mapping (address => uint) public pendingWithdrawals;
event PhunkOffered(uint indexed phunkIndex, uint minValue, address indexed toAddress);
event PhunkBidEntered(uint indexed phunkIndex, uint value, address indexed fromAddress);
event PhunkBidWithdrawn(uint indexed phunkIndex, uint value, address indexed fromAddress);
event PhunkBought(uint indexed phunkIndex, uint value, address indexed fromAddress, address indexed toAddress);
event PhunkNoLongerForSale(uint indexed phunkIndex);
/* Initializes contract with an instance of CryptoPhunks contract, and sets deployer as owner */
constructor(address initialPhunksAddress) {
if (initialPhunksAddress == address(0x0)) revert();
phunksContract = IERC721(initialPhunksAddress);
//contractOwner = msg.sender;
contractOwner = 0x25B331609e45c52eb3b069AbEb2F426D9985eF1f;
}
/* Returns the CryptoPhunks contract address currently being used */
function phunksAddress() public view returns (address) {
return address(phunksContract);
}
/* Allows the owner of the contract to set a new CryptoPhunks contract address */
function setPhunksContract(address newPhunksAddress) public {
if (msg.sender != contractOwner) revert();
phunksContract = IERC721(newPhunksAddress);
}
/* Allows the owner of a CryptoPhunks to stop offering it for sale */
function phunkNoLongerForSale(uint phunkIndex) public {
if (phunkIndex >= 10000) revert();
if (phunksContract.ownerOf(phunkIndex) != msg.sender) revert();
phunksOfferedForSale[phunkIndex] = Offer(false, phunkIndex, msg.sender, 0, address(0x0));
emit PhunkNoLongerForSale(phunkIndex);
}
/* Allows a CryptoPhunk owner to offer it for sale */
function offerPhunkForSale(uint phunkIndex, uint minSalePriceInWei) public {
if (phunkIndex >= 10000) revert();
if (phunksContract.ownerOf(phunkIndex) != msg.sender) revert();
phunksOfferedForSale[phunkIndex] = Offer(true, phunkIndex, msg.sender, minSalePriceInWei, address(0x0));
emit PhunkOffered(phunkIndex, minSalePriceInWei, address(0x0));
}
/* Allows a CryptoPhunk owner to offer it for sale to a specific address */
function offerPhunkForSaleToAddress(uint phunkIndex, uint minSalePriceInWei, address toAddress) public {
if (phunkIndex >= 10000) revert();
if (phunksContract.ownerOf(phunkIndex) != msg.sender) revert();
if (phunksContract.getApproved(phunkIndex) != address(this)) revert();
phunksOfferedForSale[phunkIndex] = Offer(true, phunkIndex, msg.sender, minSalePriceInWei, toAddress);
emit PhunkOffered(phunkIndex, minSalePriceInWei, toAddress);
}
/* Allows users to buy a CryptoPhunk offered for sale */
function buyPhunk(uint phunkIndex) payable public {
if (phunkIndex >= 10000) revert();
Offer memory offer = phunksOfferedForSale[phunkIndex];
if (!offer.isForSale) revert(); // phunk not actually for sale
if (offer.onlySellTo != address(0x0) && offer.onlySellTo != msg.sender) revert(); // phunk not supposed to be sold to this user
if (msg.value < offer.minValue) revert(); // Didn't send enough ETH
address seller = offer.seller;
if (seller != phunksContract.ownerOf(phunkIndex)) revert(); // Seller no longer owner of phunk
phunksContract.safeTransferFrom(seller, msg.sender, phunkIndex);
phunkNoLongerForSale(phunkIndex);
pendingWithdrawals[seller] += msg.value;
emit PhunkBought(phunkIndex, msg.value, seller, msg.sender);
// Check for the case where there is a bid from the new owner and refund it.
// Any other bid can stay in place.
Bid memory bid = phunkBids[phunkIndex];
if (bid.bidder == msg.sender) {
// Kill bid and refund value
pendingWithdrawals[msg.sender] += bid.value;
phunkBids[phunkIndex] = Bid(false, phunkIndex, address(0x0), 0);
}
}
/* Allows users to retrieve ETH from sales */
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
/* Allows users to enter bids for any CryptoPhunk */
function enterBidForPhunk(uint phunkIndex) payable public {
if (phunkIndex >= 10000) revert();
if (phunksContract.ownerOf(phunkIndex) == address(0x0)) revert();
if (phunksContract.ownerOf(phunkIndex) == msg.sender) revert();
if (msg.value == 0) revert();
Bid memory existing = phunkBids[phunkIndex];
if (msg.value <= existing.value) revert();
if (existing.value > 0) {
// Refund the failing bid
pendingWithdrawals[existing.bidder] += existing.value;
}
phunkBids[phunkIndex] = Bid(true, phunkIndex, msg.sender, msg.value);
emit PhunkBidEntered(phunkIndex, msg.value, msg.sender);
}
/* Allows CryptoPhunk owners to accept bids for their Phunks */
function acceptBidForPhunk(uint phunkIndex, uint minPrice) public {
if (phunkIndex >= 10000) revert();
if (phunksContract.ownerOf(phunkIndex) != msg.sender) revert();
address seller = msg.sender;
Bid memory bid = phunkBids[phunkIndex];
if (bid.value == 0) revert();
if (bid.value < minPrice) revert();
address bidder = bid.bidder;
phunksContract.safeTransferFrom(msg.sender, bidder, phunkIndex);
phunksOfferedForSale[phunkIndex] = Offer(false, phunkIndex, bidder, 0, address(0x0));
uint amount = bid.value;
phunkBids[phunkIndex] = Bid(false, phunkIndex, address(0x0), 0);
pendingWithdrawals[seller] += amount;
emit PhunkBought(phunkIndex, bid.value, seller, bidder);
}
/* Allows bidders to withdraw their bids */
function withdrawBidForPhunk(uint phunkIndex) public {
if (phunkIndex >= 10000) revert();
if (phunksContract.ownerOf(phunkIndex) == address(0x0)) revert();
if (phunksContract.ownerOf(phunkIndex) == msg.sender) revert();
Bid memory bid = phunkBids[phunkIndex];
if (bid.bidder != msg.sender) revert();
emit PhunkBidWithdrawn(phunkIndex, bid.value, msg.sender);
uint amount = bid.value;
phunkBids[phunkIndex] = Bid(false, phunkIndex, address(0x0), 0);
// Refund the bid money
payable(msg.sender).transfer(amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
}
{
"compilationTarget": {
"contracts/CryptoPhunksMarket.sol": "CryptoPhunksMarket"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"initialPhunksAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"}],"name":"PhunkBidEntered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"}],"name":"PhunkBidWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":true,"internalType":"address","name":"fromAddress","type":"address"},{"indexed":true,"internalType":"address","name":"toAddress","type":"address"}],"name":"PhunkBought","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"phunkIndex","type":"uint256"}],"name":"PhunkNoLongerForSale","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minValue","type":"uint256"},{"indexed":true,"internalType":"address","name":"toAddress","type":"address"}],"name":"PhunkOffered","type":"event"},{"inputs":[{"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"internalType":"uint256","name":"minPrice","type":"uint256"}],"name":"acceptBidForPhunk","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"phunkIndex","type":"uint256"}],"name":"buyPhunk","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"phunkIndex","type":"uint256"}],"name":"enterBidForPhunk","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"internalType":"uint256","name":"minSalePriceInWei","type":"uint256"}],"name":"offerPhunkForSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"internalType":"uint256","name":"minSalePriceInWei","type":"uint256"},{"internalType":"address","name":"toAddress","type":"address"}],"name":"offerPhunkForSaleToAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pendingWithdrawals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"phunkBids","outputs":[{"internalType":"bool","name":"hasBid","type":"bool"},{"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"internalType":"address","name":"bidder","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"phunkIndex","type":"uint256"}],"name":"phunkNoLongerForSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"phunksAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"phunksOfferedForSale","outputs":[{"internalType":"bool","name":"isForSale","type":"bool"},{"internalType":"uint256","name":"phunkIndex","type":"uint256"},{"internalType":"address","name":"seller","type":"address"},{"internalType":"uint256","name":"minValue","type":"uint256"},{"internalType":"address","name":"onlySellTo","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newPhunksAddress","type":"address"}],"name":"setPhunksContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"phunkIndex","type":"uint256"}],"name":"withdrawBidForPhunk","outputs":[],"stateMutability":"nonpayable","type":"function"}]