Created
January 20, 2023 06:47
-
-
Save KBPsystem777/8853e6ab16ef4d27e0a0aaf7b502df5f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.17; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/security/Pausable.sol"; | |
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; | |
import "./ManageLife.sol"; | |
/** | |
* @notice Marketplace contract for ManageLife. | |
* This contract the market trading of NFTs in the ML ecosystem. | |
* In real life, an NFT here represents a home or real-estate property | |
* run by ManageLife. | |
* | |
* @author https://managelife.co | |
*/ | |
contract Marketplace is ReentrancyGuard, Pausable, Ownable { | |
address public mlAdmin; | |
/// Deployer address will be considered as the ML admins | |
constructor() { | |
mlAdmin = msg.sender; | |
} | |
/// Percent divider to calculate ML's transaction earnings. | |
uint256 public constant PERCENTS_DIVIDER = 10000; | |
/** @notice Trading status. This determines if normal users will be | |
* allowed to permitted to perform market trading (Bidding, Selling, Buy). | |
* By default Admin wallet will perform all these functions on behalf of all customers | |
* due to legal requirements. | |
* Once legal landscape permits, customers will be able to perform market trading by themselves. | |
*/ | |
bool public allowTrading = false; | |
/// instance of the MLIFE NFT contract. | |
ManageLife public mLife; | |
struct Offer { | |
uint32 tokenId; | |
address seller; | |
uint256 price; | |
address offeredTo; | |
} | |
struct Bid { | |
address bidder; | |
uint256 value; | |
} | |
/// Default admin fee. | |
uint256 public adminPercent = 200; | |
/// Status for adming pending claimable earnings. | |
uint256 public adminPending; | |
/// Mapping of MLIFE tokenIds to Offers | |
mapping(uint256 => Offer) public offers; | |
/// Mapping of MLIFE tokenIds to Bids | |
mapping(uint256 => Bid) public bids; | |
event Offered( | |
uint256 indexed tokenId, | |
uint256 price, | |
address indexed toAddress | |
); | |
event BidEntered( | |
uint32 indexed tokenId, | |
uint256 value, | |
address indexed fromAddress | |
); | |
event BidCancelled( | |
uint256 indexed tokenId, | |
uint256 value, | |
address indexed bidder | |
); | |
event Bought( | |
uint256 indexed tokenId, | |
uint256 value, | |
address indexed fromAddress, | |
address indexed toAddress, | |
bool isInstant | |
); | |
event BidWithdrawn(uint256 indexed tokenId, uint256 value); | |
event Cancelled(uint256 indexed tokenId); | |
event TradingStatus(bool _isTradingAllowed); | |
event Received(address, uint); | |
error InvalidPercent(uint256 _percent, uint256 minimumPercent); | |
/// @notice Security feature to Pause smart contracts transactions | |
function pause() external whenNotPaused onlyOwner { | |
_pause(); | |
} | |
/// @notice Unpausing the Paused transactions feature. | |
function unpause() external whenPaused onlyOwner { | |
_unpause(); | |
} | |
/** | |
* @notice Update the `allowTrading` status to true/false. | |
* @dev Can only be executed by contract owner. Will emit TradingStatus event. | |
* @param _isTradingAllowed New boolean status to set. | |
*/ | |
function setTrading(bool _isTradingAllowed) external onlyOwner { | |
allowTrading = _isTradingAllowed; | |
emit TradingStatus(_isTradingAllowed); | |
} | |
/** | |
* @notice Set the MLIFE contract. | |
* @dev Important to set this after deployment. Only MLIFE address is needed. | |
* Will not access 0x0 (zero/invalid) address. | |
* @param nftAddress Address of MLIFE contract. | |
*/ | |
function setNftContract(address nftAddress) external onlyOwner { | |
require(nftAddress != address(0x0), "Zero address"); | |
mLife = ManageLife(nftAddress); | |
} | |
/** | |
* @notice Allows admin wallet to set new percentage fee. | |
* @dev This throws an error is the new percentage is less than 500. | |
* @param _percent New admin percentage. | |
*/ | |
function setAdminPercent(uint256 _percent) external onlyOwner { | |
if (_percent < 500) { | |
revert InvalidPercent({_percent: _percent, minimumPercent: 500}); | |
} | |
adminPercent = _percent; | |
} | |
/*Allows the owner of the contract to withdraw pending ETH */ | |
/** | |
* @notice Withdraw marketplace earnings. | |
* @dev Can only be triggered by the admin wallet or contract owner. | |
* This will transfer the market earning to the admin wallet. | |
*/ | |
function withdraw() external onlyOwner nonReentrant { | |
uint256 amount = adminPending; | |
adminPending = 0; | |
_safeTransferETH(mlAdmin, amount); | |
} | |
/** | |
* @notice Cancel and existing sale offer. | |
* | |
* @dev Once triggered, the offer struct for this tokenId will be destroyed. | |
* Can only be called by MLIFE holders. The caller of this function should be | |
* the owner if the NFT in MLIFE contract. | |
* | |
* @param tokenId TokenId of the NFT. | |
*/ | |
function cancelForSale(uint32 tokenId) external onlyMLifeOwner { | |
require( | |
msg.sender == mLife.ownerOf(tokenId), | |
"You are not the owner of this token" | |
); | |
delete offers[tokenId]; | |
emit Cancelled(tokenId); | |
} | |
/** | |
* @notice Offer a property or NFT for sale in the marketplace. | |
* | |
* @dev If `allowTrading` is equals to true, | |
* users are allowed to execute this function. Else, admin wallet will facilitate | |
* the offering sale on their behalf. | |
* | |
* @param tokenId MLIFE tokenId to be put on sale. | |
* @param minSalePrice Minimum sale price of the property. | |
*/ | |
function offerForSale( | |
uint32 tokenId, | |
uint256 minSalePrice | |
) external whenNotPaused { | |
if (allowTrading == false) { | |
require( | |
msg.sender == mlAdmin, | |
"Trading is disabled at this moment, only Admin can trade" | |
); | |
offers[tokenId] = Offer( | |
tokenId, | |
msg.sender, | |
minSalePrice, | |
address(0x0) | |
); | |
emit Offered(tokenId, minSalePrice, address(0x0)); | |
} else { | |
require( | |
msg.sender == mLife.ownerOf(tokenId), | |
"You do not own this MLIFE" | |
); | |
offers[tokenId] = Offer( | |
tokenId, | |
msg.sender, | |
minSalePrice, | |
address(0x0) | |
); | |
emit Offered(tokenId, minSalePrice, address(0x0)); | |
} | |
} | |
/** | |
* @notice Offer a property for sale to a specific wallet address only. | |
* | |
* @dev Allows MLIFE holders to sell their property to a specific wallet address. | |
* By default, this process is being performed by the admin wallet on behalf of the customers | |
* not until the `allowTrading` has been set to `true`. | |
* | |
* @param tokenId TokenId of the property to be offered. | |
* @param minSalePrice Minimum sale prices of the property. | |
* @param toAddress Wallet address on where the property will be offered to. | |
*/ | |
function offerForSaleToAddress( | |
uint32 tokenId, | |
uint256 minSalePrice, | |
address toAddress | |
) external whenNotPaused { | |
if (allowTrading == false) { | |
require( | |
msg.sender == mlAdmin, | |
"Trading is disabled at this moment, only Admin can trade" | |
); | |
offers[tokenId] = Offer( | |
tokenId, | |
msg.sender, | |
minSalePrice, | |
toAddress | |
); | |
} else { | |
require( | |
msg.sender == mLife.ownerOf(tokenId), | |
"You do not own this MLIFE" | |
); | |
offers[tokenId] = Offer( | |
tokenId, | |
msg.sender, | |
minSalePrice, | |
toAddress | |
); | |
emit Offered(tokenId, minSalePrice, toAddress); | |
} | |
} | |
/** | |
* @notice Allows users to buy a property that is registered in ML. | |
* | |
* @dev By default, this operation is disabled for customers. | |
* Only admin wallet can perform this on their behalf until the | |
* `allowTrading` variable is equals to true. | |
* | |
* @param tokenId TokenId of the property. | |
*/ | |
function buy(uint32 tokenId) external payable whenNotPaused { | |
if (allowTrading == false) { | |
require( | |
msg.sender == mlAdmin, | |
"Trading is disabled at this moment, only Admin can trade" | |
); | |
Offer memory offer = offers[tokenId]; | |
uint256 amount = msg.value; | |
require( | |
offer.offeredTo == address(0x0) || | |
offer.offeredTo == msg.sender, | |
"This offer is not for you" | |
); | |
require(amount == offer.price, "Not enough ether"); | |
address seller = offer.seller; | |
require(seller != msg.sender, "Seller == msg.sender"); | |
require( | |
seller == mLife.ownerOf(tokenId), | |
"Seller is not an MLIFE owner" | |
); | |
offers[tokenId] = Offer(tokenId, msg.sender, 0, address(0x0)); | |
/// Transfer to msg.sender from seller. | |
mLife.transferFrom(seller, msg.sender, tokenId); | |
/// Transfer commission to admin | |
uint256 commission = 0; | |
if (adminPercent > 0) { | |
commission = (amount * adminPercent) / PERCENTS_DIVIDER; | |
adminPending += commission; | |
} | |
/// Transfer ETH to seller | |
_safeTransferETH(seller, amount - commission); | |
emit Bought(tokenId, amount, seller, msg.sender, true); | |
/// Refund bid if new owner is buyer | |
Bid memory bid = bids[tokenId]; | |
if (bid.bidder == msg.sender) { | |
_safeTransferETH(bid.bidder, bid.value); | |
emit BidCancelled(tokenId, bid.value, bid.bidder); | |
bids[tokenId] = Bid(address(0x0), 0); | |
} | |
} else { | |
Offer memory offer = offers[tokenId]; | |
uint256 amount = msg.value; | |
require( | |
offer.offeredTo == address(0x0) || | |
offer.offeredTo == msg.sender, | |
"This offer is not for you" | |
); | |
require(amount == offer.price, "Not enough ether"); | |
address seller = offer.seller; | |
require(seller != msg.sender, "Seller == msg.sender"); | |
require( | |
seller == mLife.ownerOf(tokenId), | |
"Seller is not an MLIFE owner" | |
); | |
offers[tokenId] = Offer(tokenId, msg.sender, 0, address(0x0)); | |
/// Transfer to msg.sender from seller. | |
mLife.transferFrom(seller, msg.sender, tokenId); | |
/// Transfer commission to admin! | |
uint256 commission = 0; | |
if (adminPercent > 0) { | |
commission = (amount * adminPercent) / PERCENTS_DIVIDER; | |
adminPending += commission; | |
} | |
/// Transfer ETH to seller! | |
_safeTransferETH(seller, amount - commission); | |
emit Bought(tokenId, amount, seller, msg.sender, true); | |
/// Refund bid if new owner is buyer | |
Bid memory bid = bids[tokenId]; | |
if (bid.bidder == msg.sender) { | |
_safeTransferETH(bid.bidder, bid.value); | |
emit BidCancelled(tokenId, bid.value, bid.bidder); | |
bids[tokenId] = Bid(address(0x0), 0); | |
} | |
} | |
} | |
/** | |
* @notice Allows users to submit a bid to any properties. | |
* | |
* @dev By default, bidding is disabled for customers. | |
* Only admin wallet can perform bidding on their behalf until the | |
* `allowTrading` variable is equals to true. | |
* | |
* @param tokenId tokenId of the property. | |
*/ | |
function placeBid( | |
uint32 tokenId | |
) external payable whenNotPaused nonReentrant { | |
if (allowTrading == false) { | |
require( | |
msg.sender == mlAdmin, | |
"Trading is disabled at this moment, only Admin can trade" | |
); | |
require(msg.value != 0, "Cannot enter bid of zero"); | |
Bid memory existing = bids[tokenId]; | |
require(msg.value > existing.value, "Your bid is too low"); | |
if (existing.value > 0) { | |
/// Refund existing bid | |
_safeTransferETH(existing.bidder, existing.value); | |
emit BidCancelled(tokenId, existing.value, existing.bidder); | |
} | |
bids[tokenId] = Bid(msg.sender, msg.value); | |
emit BidEntered(tokenId, msg.value, msg.sender); | |
} else { | |
require(mLife.balanceOf(msg.sender) >= 1, "Only for MLIFE owners"); | |
require(msg.value != 0, "Cannot enter bid of zero"); | |
Bid memory existing = bids[tokenId]; | |
require(msg.value > existing.value, "Your bid is too low"); | |
if (existing.value > 0) { | |
/// Refund existing bid | |
_safeTransferETH(existing.bidder, existing.value); | |
emit BidCancelled(tokenId, existing.value, existing.bidder); | |
} | |
bids[tokenId] = Bid(msg.sender, msg.value); | |
emit BidEntered(tokenId, msg.value, msg.sender); | |
} | |
} | |
/** | |
* @notice Allows home owners to accept bids on their properties | |
* @param tokenId tokenId of the property. | |
* @param minPrice Minimum bidding price. | |
*/ | |
function acceptBid( | |
uint32 tokenId, | |
uint256 minPrice | |
) external whenNotPaused { | |
if (allowTrading == false) { | |
require( | |
msg.sender == mlAdmin, | |
"Trading is disabled at this moment, only Admin can trade" | |
); | |
address seller = msg.sender; | |
Bid memory bid = bids[tokenId]; | |
uint256 amount = bid.value; | |
require(amount != 0, "Cannot enter bid of zero"); | |
require(amount >= minPrice, "the bid is too low"); | |
address bidder = bid.bidder; | |
require(seller != bidder, "You already own this token"); | |
offers[tokenId] = Offer(tokenId, bidder, 0, address(0x0)); | |
bids[tokenId] = Bid(address(0x0), 0); | |
/// Transfer MLIFE to Bidder | |
mLife.transferFrom(msg.sender, bidder, tokenId); | |
uint256 commission = 0; | |
/// Transfering commission fee to admin wallet | |
if (adminPercent > 0) { | |
commission = (amount * adminPercent) / PERCENTS_DIVIDER; | |
adminPending += commission; | |
} | |
/// Transfer ETH to seller | |
_safeTransferETH(seller, amount - commission); | |
emit Bought(tokenId, bid.value, seller, bidder, false); | |
} else { | |
require( | |
msg.sender == mLife.ownerOf(tokenId), | |
"You do not own this MLIFE" | |
); | |
address seller = msg.sender; | |
Bid memory bid = bids[tokenId]; | |
uint256 amount = bid.value; | |
require(amount != 0, "Cannot enter bid of zero"); | |
require(amount >= minPrice, "The bid is too low"); | |
address bidder = bid.bidder; | |
require(seller != bidder, "You already own this token"); | |
offers[tokenId] = Offer(tokenId, bidder, 0, address(0x0)); | |
bids[tokenId] = Bid(address(0x0), 0); | |
/// Transfer MLIFE NFT to the Bidder | |
mLife.transferFrom(msg.sender, bidder, tokenId); | |
uint256 commission = 0; | |
/// Transfer Commission to admin wallet | |
if (adminPercent > 0) { | |
commission = (amount * adminPercent) / PERCENTS_DIVIDER; | |
adminPending += commission; | |
} | |
/// Transfer ETH to seller | |
_safeTransferETH(seller, amount - commission); | |
emit Bought(tokenId, bid.value, seller, bidder, false); | |
} | |
} | |
/** | |
* @notice Allows bidders to withdraw their bid on a specific property. | |
* | |
* @dev There will be different process flows on this function depending | |
* on the value of `allowTrading`. By default, the entire trading will be | |
* facilitated by the admin wallet. | |
* | |
* @param tokenId tokenId of the property that is currently being bid. | |
*/ | |
function withdrawBid(uint32 tokenId) external nonReentrant { | |
if (allowTrading == false) { | |
require( | |
msg.sender == mlAdmin, | |
"Trading is disabled at this moment, only Admin can trade" | |
); | |
Bid memory bid = bids[tokenId]; | |
uint256 amount = bid.value; | |
emit BidWithdrawn(tokenId, amount); | |
bids[tokenId] = Bid(address(0x0), 0); | |
_safeTransferETH(msg.sender, amount); | |
} else { | |
Bid memory bid = bids[tokenId]; | |
require( | |
bid.bidder == msg.sender, | |
"The Sender is not original bidder" | |
); | |
uint256 amount = bid.value; | |
emit BidWithdrawn(tokenId, amount); | |
bids[tokenId] = Bid(address(0x0), 0); | |
_safeTransferETH(msg.sender, amount); | |
} | |
} | |
/** | |
* @dev This records the address and ether value that was sent to the Marketplace | |
*/ | |
receive() external payable { | |
emit Received(msg.sender, msg.value); | |
} | |
/// @dev Eth transfer hook | |
function _safeTransferETH(address to, uint256 value) internal { | |
(bool success, ) = to.call{value: value}(""); | |
require( | |
success, | |
"Address: unable to send value, recipient may have reverted" | |
); | |
} | |
/** | |
* @notice Allow admins to set new ML Admin wallet. | |
* Only contract owner/deployer can execute this function | |
* | |
* @param newAdminAddress New wallet address to be used. | |
*/ | |
function setMLAdmin(address newAdminAddress) external onlyOwner { | |
mlAdmin = newAdminAddress; | |
} | |
/** | |
* @notice Modifier to make sure only MLIFE | |
* NFT holders can run a specific functions. | |
*/ | |
modifier onlyMLifeOwner() { | |
require(mLife.balanceOf(msg.sender) >= 0, "Only for the MLIFE owner"); | |
_; | |
} | |
/** | |
* @notice Modifier to make sure only admin wallet can perform | |
* market tradings on behalf of all users. | |
* | |
* @dev `allowTrading` should be set to `true` in order for the users to facilitate the | |
* market trading by themselves. | |
*/ | |
modifier isTradingAllowed() { | |
if (allowTrading == false) { | |
require( | |
msg.sender == mlAdmin, | |
"Trading is disabled, only Admin can trade." | |
); | |
_; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment