Skip to content

Instantly share code, notes, and snippets.

@kumarabhirup
Created December 10, 2021 03:24
Show Gist options
  • Save kumarabhirup/b47500e1dde94a1c039095193cfdbf30 to your computer and use it in GitHub Desktop.
Save kumarabhirup/b47500e1dde94a1c039095193cfdbf30 to your computer and use it in GitHub Desktop.
Contract that allows participants to place bids on NFTs, and also allows Owners to list NFTs at Reserve Price at various durations.
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.4.22 <0.9.0;
import "./NFT.sol";
contract NFTMarketplace is NFT {
constructor (
string memory name_,
string memory symbol_,
address _permissionManagementContractAddress
)
NFT(name_, symbol_, _permissionManagementContractAddress)
public {
//
}
/** Fee Structure */
uint256 public commissionOnEverySaleInPercent = 2;
function changeCommissionOnEverySaleInPercent(uint256 _commissionOnEverySaleInPercent)
public
returns(uint256)
{
permissionManagement.adminOnlyMethod(msg.sender);
commissionOnEverySaleInPercent = _commissionOnEverySaleInPercent;
return _commissionOnEverySaleInPercent;
}
/** Token Data */
mapping(uint256 => uint256) public tokenPrice;
mapping(uint256 => bool) public tokenAutoSellEnabled;
/** Overrides */
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override(NFT) {
super._beforeTokenTransfer(from, to, tokenId);
// if the token is transferred anytime (except when minted), disableAutoSell
if (from != address(0)) {
_disableAutoSell(tokenId);
}
}
event EnabledAutoSell(
uint256 indexed _tokenId,
uint256 _price,
address indexed _enabledBy
);
event DisabledAutoSell(
uint256 indexed _tokenId,
address indexed _disabledBy
);
/** Marketplace */
function enableAutoSell(
uint256 _tokenId,
uint256 _pricePerToken
) public returns(uint256, uint256) {
permissionManagement.adhereToBanMethod(msg.sender);
require(_exists(_tokenId), "Token does not exist");
require(ownerOf(_tokenId) == msg.sender || getApproved(_tokenId) == msg.sender || permissionManagement.moderators(msg.sender) == true, "You do not have the right to enableAutoSell for this token");
tokenPrice[_tokenId] = _pricePerToken;
tokenAutoSellEnabled[_tokenId] = true;
emit EnabledAutoSell(
_tokenId,
_pricePerToken,
msg.sender
);
return (_tokenId, _pricePerToken);
}
function _disableAutoSell(
uint256 _tokenId
) internal returns(uint256) {
tokenAutoSellEnabled[_tokenId] = false;
emit DisabledAutoSell(
_tokenId,
msg.sender
);
return _tokenId;
}
function disableAutoSell(
uint256 _tokenId
) public returns(uint256) {
permissionManagement.adhereToBanMethod(msg.sender);
require(_exists(_tokenId), "Token does not exist");
require(ownerOf(_tokenId) == msg.sender || getApproved(_tokenId) == msg.sender || permissionManagement.moderators(msg.sender) == true, "You do not have the right to disableAutoSell for this token");
return _disableAutoSell(_tokenId);
}
struct Order {
uint256 id;
address payable buyer;
uint256 tokenId;
uint256 price;
uint256 expiresAt;
address payable placedBy;
}
enum OrderStatus { PLACED, EXECUTED, CANCELLED }
Order[] public orders;
// tokenId to Orders mapping
mapping (uint256 => Order[]) public ordersByTokenId;
// orderId to isExecuted mapping
mapping (uint256 => OrderStatus) public orderStatus;
event OrderPlaced(
uint256 indexed id,
address indexed buyer,
uint256 indexed tokenId,
uint256 price,
uint256 expiresAt,
address placedBy
);
event OrderExecuted(
uint256 indexed id
);
event OrderCancelled(
uint256 indexed id
);
function _placeOrder(
uint256 _tokenId,
uint256 _expireInSeconds
) validGasPrice private returns(uint256) {
require(_exists(_tokenId), "Token does not exist");
require(ownerOf(_tokenId) != msg.sender, "You cannot place an order on your own token");
uint256 _orderId = orders.length;
Order memory _order = Order({
id: _orderId,
buyer: payable(msg.sender),
tokenId: _tokenId,
price: msg.value,
expiresAt: block.timestamp + _expireInSeconds,
placedBy: payable(msg.sender)
});
orders.push(_order);
ordersByTokenId[_tokenId].push(_order);
orderStatus[_orderId] = OrderStatus.PLACED;
emit OrderPlaced(
_order.id,
_order.buyer,
_order.tokenId,
_order.price,
_order.expiresAt,
_order.placedBy
);
return _orderId;
}
function _placeOffer(
uint256 _tokenId,
uint256 _expireInSeconds,
address _buyer,
uint256 _price
) validGasPrice private returns(uint256) {
require(_exists(_tokenId), "Token does not exist");
require(ownerOf(_tokenId) == msg.sender || getApproved(_tokenId) == msg.sender, "You do not have rights to offer this token");
require(_buyer != msg.sender, "You cannot make an offer to yourself");
uint256 _orderId = orders.length;
Order memory _order = Order({
id: _orderId,
buyer: payable(_buyer),
tokenId: _tokenId,
price: _price,
expiresAt: block.timestamp + _expireInSeconds,
placedBy: payable(msg.sender)
});
orders.push(_order);
ordersByTokenId[_tokenId].push(_order);
orderStatus[_orderId] = OrderStatus.PLACED;
emit OrderPlaced(
_order.id,
_order.buyer,
_order.tokenId,
_order.price,
_order.expiresAt,
_order.placedBy
);
return _orderId;
}
function _executeOrder(
uint256 _orderId
) validGasPrice private returns(uint256) {
require(orderStatus[_orderId] != OrderStatus.CANCELLED, "Order already cancelled");
require(block.timestamp <= orders[_orderId].expiresAt, "Order expired");
require(orders[_orderId].price <= msg.value || orders[_orderId].price <= super.getBalance(), string(abi.encodePacked("Contract does not have sufficient balance for this order to be executed, Balance: ", super.uintToStr(super.getBalance()))));
if (orders[_orderId].price > 0) {
uint256 beneficiaryPay = (orders[_orderId].price * commissionOnEverySaleInPercent) / 100;
(bool success1, ) = permissionManagement.beneficiary().call{value: beneficiaryPay}("");
require(success1, "Transfer to Beneficiary failed.");
(bool success2, ) = payable(super.ownerOf(orders[_orderId].tokenId)).call{value: orders[_orderId].price - beneficiaryPay}("");
require(success2, "Transfer to Token Owner failed.");
}
orderStatus[_orderId] = OrderStatus.EXECUTED;
emit OrderExecuted(_orderId);
return _orderId;
}
function _cancelOrder(
uint256 _orderId
) validGasPrice private returns(uint256) {
require(orderStatus[_orderId] != OrderStatus.CANCELLED, "Order already cancelled");
require(orderStatus[_orderId] != OrderStatus.EXECUTED, "Order was already executed, cannot be cancelled now");
require(orderStatus[_orderId] == OrderStatus.PLACED, "Order must be placed to cancel it");
if (orders[_orderId].price != 0 && orders[_orderId].placedBy == orders[_orderId].buyer) {
(bool success, ) = orders[_orderId].buyer.call{value: orders[_orderId].price}("");
require(success, "Transfer to Buyer failed.");
}
orderStatus[_orderId] = OrderStatus.CANCELLED;
emit OrderCancelled(_orderId);
return _orderId;
}
function placeOrder(
uint256 _tokenId,
uint256 _expireInSeconds
) validGasPrice public payable returns(uint256) {
permissionManagement.adhereToBanMethod(msg.sender);
require(_exists(_tokenId), "placeOrder query for nonexistent token");
require(_expireInSeconds >= 60, "Order must not expire within 60 seconds");
uint256 _orderId = _placeOrder(_tokenId, _expireInSeconds);
// check if token is sellable
address payable tokenOwner = payable(ownerOf(_tokenId));
bool istokenAvailableForImmediateBuying = tokenAutoSellEnabled[_tokenId];
// if sellable, buy
if (istokenAvailableForImmediateBuying == true) {
// if free, complete transaction
if (tokenPrice[_tokenId] == 0) {
_executeOrder(_orderId);
_transfer(tokenOwner, msg.sender, _tokenId);
return _orderId;
}
// check if offerPrice matches tokenPrice, if yes, complete transaction.
if (msg.value >= tokenPrice[_tokenId]) {
_executeOrder(_orderId);
_transfer(tokenOwner, msg.sender, _tokenId);
return _orderId;
}
}
return _orderId;
}
function placeOffer(
uint256 _tokenId,
uint256 _expireInSeconds,
address _buyer,
uint256 _price
) validGasPrice public returns(uint256) {
permissionManagement.adhereToBanMethod(msg.sender);
require(_exists(_tokenId), "placeOffer query for nonexistent token");
require(_expireInSeconds >= 60, "Offer must not expire within 60 seconds");
uint256 _orderId = _placeOffer(_tokenId, _expireInSeconds, _buyer, _price);
return _orderId;
}
function acceptOffer(
uint256 _orderId
) validGasPrice public payable returns(uint256) {
permissionManagement.adhereToBanMethod(msg.sender);
Order memory _order = orders[_orderId];
address tokenOwner = ownerOf(_order.tokenId);
address tokenApprovedAddress = getApproved(_order.tokenId);
require(_order.placedBy != msg.sender, "You cannot accept your own offer");
// if buyer offered the token owner
if (_order.placedBy == _order.buyer) {
require(tokenOwner == msg.sender || tokenApprovedAddress == msg.sender, "Only token owner can accept this offer");
_executeOrder(_orderId);
_transfer(tokenOwner, _order.buyer, _order.tokenId);
} else {
// if token owner/approved address, offered the buyer
require(_order.buyer == msg.sender, "Only the address that was offered can accept this offer");
require(_order.placedBy == tokenOwner, "Offer expired as the token is no more owned by the original offerer");
// require offer price
require(msg.value >= _order.price, "Insufficient amount sent");
// send excess amount to beneficiary
if (msg.value - _order.price > 0) {
(bool success, ) = permissionManagement.beneficiary().call{value: msg.value - _order.price}("");
require(success, "Transfer to Beneficiary failed.");
}
_executeOrder(_orderId);
_transfer(tokenOwner, _order.buyer, _order.tokenId);
return _orderId;
}
return _orderId;
}
function cancelOffer(
uint256 _orderId
) validGasPrice public returns(uint256) {
permissionManagement.adhereToBanMethod(msg.sender);
Order memory _order = orders[_orderId];
address tokenOwner = ownerOf(_order.tokenId);
address tokenApprovedAddress = getApproved(_order.tokenId);
require(_order.placedBy == msg.sender || tokenOwner == msg.sender || tokenApprovedAddress == msg.sender, "You do not have the right to cancel this offer");
_cancelOrder(_orderId);
return _orderId;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment