403 lines
11 KiB
Solidity
403 lines
11 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.10;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
|
|
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
|
|
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
|
|
contract BENftMarket is Ownable, ReentrancyGuard, ERC1155Holder, ERC721Holder {
|
|
using SafeERC20 for IERC20;
|
|
|
|
struct OrderInfo {
|
|
uint256 orderId;
|
|
uint256 tokenId;
|
|
uint256 amount;
|
|
address owner;
|
|
uint256 price;
|
|
address nftToken;
|
|
address currency;
|
|
}
|
|
mapping(address => bool) public erc721Supported;
|
|
mapping(address => bool) public erc1155Supported;
|
|
mapping(address => bool) public erc721SupportedHistory;
|
|
mapping(address => bool) public erc1155SupportedHistory;
|
|
mapping(address => bool) public erc20Supported;
|
|
mapping(uint256 => OrderInfo) public orderInfos;
|
|
mapping(address => uint256) public nftPriceMaxLimit;
|
|
mapping(address => uint256) public nftPriceMinLimit;
|
|
|
|
event SellOrder(
|
|
uint256 indexed tokenId,
|
|
address indexed owner,
|
|
address indexed nftToken,
|
|
uint256 amount,
|
|
uint256 orderId,
|
|
address currency,
|
|
uint256 price
|
|
);
|
|
|
|
event CancelOrder(
|
|
uint256 indexed orderId,
|
|
address indexed nftToken,
|
|
uint256 indexed tokenId
|
|
);
|
|
|
|
event PriceUpdate(
|
|
uint256 indexed orderId,
|
|
address indexed nftToken,
|
|
uint256 indexed tokenId,
|
|
uint256 priceOld,
|
|
uint256 price
|
|
);
|
|
|
|
event BuyOrder(
|
|
uint256 indexed tokenId,
|
|
uint256 orderId,
|
|
address nftToken,
|
|
uint256 amount,
|
|
address seller,
|
|
address buyer,
|
|
address erc20,
|
|
uint256 price
|
|
);
|
|
|
|
event AddNFTSuppout(address nftToken);
|
|
event RemoveNFTSuppout(address nftToken);
|
|
event AddERC20Suppout(address erc20);
|
|
event RemoveERC20Suppout(address erc20);
|
|
|
|
uint256 public tranFeeTotal;
|
|
uint256 public tranTaxTotal;
|
|
|
|
uint256 constant ROUND = 1000000;
|
|
uint256 public transactionFee = (3 * ROUND) / 100;
|
|
// min transaction fee is: 0
|
|
uint256 public constant MIN_TRANSACTION_FEE = 0;
|
|
// max transaction fee is: 10%
|
|
uint256 public constant MAX_TRANSACTION_FEE = (10 * ROUND) / 100;
|
|
|
|
uint256 public transactionTax = (1 * ROUND) / 100;
|
|
// min transaction tax is: 0
|
|
uint256 public constant MIN_TRANSACTION_TAX = 0;
|
|
// max transaction tax is: 10%
|
|
uint256 public constant MAX_TRANSACTION_TAX = (10 * ROUND) / 100;
|
|
|
|
address public feeToAddress;
|
|
|
|
address public taxToAddress;
|
|
|
|
uint256 public incrId;
|
|
|
|
function sell(
|
|
address nftToken,
|
|
address currency,
|
|
uint256 tokenId,
|
|
uint256 price,
|
|
uint256 amount
|
|
) external {
|
|
require(tokenId != 0, "NFTMarket: tokenId can not be 0!");
|
|
require(
|
|
erc721Supported[nftToken] || erc1155Supported[nftToken],
|
|
"NFTMarket: Unsupported NFT"
|
|
);
|
|
require(erc20Supported[currency], "NFTMarket: Unsupported tokens");
|
|
require(
|
|
price <= nftPriceMaxLimit[nftToken] || nftPriceMaxLimit[nftToken] == 0,
|
|
"NFTMarket: Maximum price limit exceeded"
|
|
);
|
|
require(
|
|
price >= nftPriceMinLimit[nftToken],
|
|
"NFTMarket: Below the minimum price limit"
|
|
);
|
|
incrId += 1;
|
|
OrderInfo storage orderInfo = orderInfos[incrId];
|
|
orderInfo.orderId = incrId;
|
|
orderInfo.tokenId = tokenId;
|
|
orderInfo.amount = amount;
|
|
orderInfo.nftToken = nftToken;
|
|
orderInfo.owner = _msgSender();
|
|
orderInfo.price = price;
|
|
orderInfo.currency = currency;
|
|
if (erc721Supported[nftToken]) {
|
|
require(amount == 1, "NFTMarket: ERC721 amount must be 1 ");
|
|
IERC721(nftToken).safeTransferFrom(_msgSender(), address(this), tokenId);
|
|
} else if (erc1155Supported[nftToken]) {
|
|
IERC1155(nftToken).safeTransferFrom(
|
|
_msgSender(),
|
|
address(this),
|
|
tokenId,
|
|
amount,
|
|
""
|
|
);
|
|
}
|
|
|
|
emit SellOrder(
|
|
tokenId,
|
|
_msgSender(),
|
|
nftToken,
|
|
amount,
|
|
incrId,
|
|
currency,
|
|
price
|
|
);
|
|
}
|
|
|
|
function buy(uint256 orderId, uint256 price) external nonReentrant {
|
|
require(orderId > 0 && orderId <= incrId, "NFTMarket: orderId error");
|
|
OrderInfo memory orderInfo = orderInfos[orderId];
|
|
require(orderInfo.orderId != 0, "NFTMarket: order info does not exist");
|
|
require(orderInfo.price == price, "NFTMarket: Price error");
|
|
uint256 _transactionFee = (orderInfo.price * transactionFee) / ROUND;
|
|
tranFeeTotal = tranFeeTotal + _transactionFee;
|
|
uint256 _transactionTax = (orderInfo.price * transactionTax) / ROUND;
|
|
tranTaxTotal = tranTaxTotal + _transactionTax;
|
|
uint256 _amount = orderInfo.price - _transactionFee - _transactionTax;
|
|
IERC20 paymentContract = IERC20(orderInfo.currency);
|
|
require(
|
|
paymentContract.balanceOf(_msgSender()) >= orderInfo.price,
|
|
"BENFTMarket: buyer doesn't have enough token to buy this item"
|
|
);
|
|
require(
|
|
paymentContract.allowance(_msgSender(), address(this)) >= orderInfo.price,
|
|
"BENFTMarket: buyer doesn't approve marketplace to spend payment amount"
|
|
);
|
|
paymentContract.safeTransferFrom(_msgSender(), orderInfo.owner, _amount);
|
|
if (_transactionFee > 0) {
|
|
paymentContract.safeTransferFrom(
|
|
_msgSender(),
|
|
feeToAddress,
|
|
_transactionFee
|
|
);
|
|
}
|
|
if (_transactionTax > 0) {
|
|
paymentContract.safeTransferFrom(
|
|
_msgSender(),
|
|
taxToAddress,
|
|
_transactionTax
|
|
);
|
|
}
|
|
|
|
if (
|
|
erc721Supported[orderInfo.nftToken] ||
|
|
erc721SupportedHistory[orderInfo.nftToken]
|
|
) {
|
|
IERC721(orderInfo.nftToken).safeTransferFrom(
|
|
address(this),
|
|
_msgSender(),
|
|
orderInfo.tokenId
|
|
);
|
|
} else if (
|
|
erc1155Supported[orderInfo.nftToken] ||
|
|
erc1155SupportedHistory[orderInfo.nftToken]
|
|
) {
|
|
IERC1155(orderInfo.nftToken).safeTransferFrom(
|
|
address(this),
|
|
_msgSender(),
|
|
orderInfo.tokenId,
|
|
orderInfo.amount,
|
|
""
|
|
);
|
|
}
|
|
|
|
emit BuyOrder(
|
|
orderInfo.tokenId,
|
|
orderId,
|
|
orderInfo.nftToken,
|
|
orderInfo.amount,
|
|
orderInfo.owner,
|
|
_msgSender(),
|
|
orderInfo.currency,
|
|
orderInfo.price
|
|
);
|
|
delete orderInfos[orderId];
|
|
}
|
|
|
|
function cancelOrder(uint256 orderId) external nonReentrant {
|
|
require(orderId > 0 && orderId <= incrId, "NFTMarket: orderId error");
|
|
OrderInfo memory orderInfo = orderInfos[orderId];
|
|
require(orderInfo.orderId != 0, "NFTMarket: NFT does not exist");
|
|
require(orderInfo.owner == _msgSender(), "NFTMarket: caller is not owner");
|
|
if (
|
|
erc721Supported[orderInfo.nftToken] ||
|
|
erc721SupportedHistory[orderInfo.nftToken]
|
|
) {
|
|
IERC721(orderInfo.nftToken).safeTransferFrom(
|
|
address(this),
|
|
_msgSender(),
|
|
orderInfo.tokenId
|
|
);
|
|
} else if (
|
|
erc1155Supported[orderInfo.nftToken] ||
|
|
erc1155SupportedHistory[orderInfo.nftToken]
|
|
) {
|
|
IERC1155(orderInfo.nftToken).safeTransferFrom(
|
|
address(this),
|
|
_msgSender(),
|
|
orderInfo.tokenId,
|
|
orderInfo.amount,
|
|
""
|
|
);
|
|
}
|
|
delete orderInfos[orderId];
|
|
|
|
emit CancelOrder(orderId, orderInfo.nftToken, orderInfo.tokenId);
|
|
}
|
|
|
|
function updatePrice(uint256 orderId, uint256 price) external {
|
|
require(orderId > 0 && orderId <= incrId, "NFTMarket: orderId error");
|
|
OrderInfo storage orderInfo = orderInfos[orderId];
|
|
require(orderInfo.orderId != 0, "NFTMarket: NFT does not exist");
|
|
require(orderInfo.owner == _msgSender(), "NFTMarket: caller is not owner");
|
|
require(
|
|
price <= nftPriceMaxLimit[orderInfo.nftToken] ||
|
|
nftPriceMaxLimit[orderInfo.nftToken] == 0,
|
|
"NFTMarket: Maximum price limit exceeded"
|
|
);
|
|
require(
|
|
price >= nftPriceMinLimit[orderInfo.nftToken],
|
|
"NFTMarket: Below the minimum price limit"
|
|
);
|
|
uint256 priceOld = orderInfo.price;
|
|
orderInfo.price = price;
|
|
emit PriceUpdate(
|
|
orderId,
|
|
orderInfo.nftToken,
|
|
orderInfo.tokenId,
|
|
priceOld,
|
|
price
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Add ERC20 support
|
|
*/
|
|
function addERC721Support(address nftToken) external onlyOwner {
|
|
erc721Supported[nftToken] = true;
|
|
emit AddNFTSuppout(nftToken);
|
|
}
|
|
|
|
/**
|
|
* @dev Remove 721 NFT support
|
|
*/
|
|
function removeERC721Support(address nftToken) external onlyOwner {
|
|
erc721Supported[nftToken] = false;
|
|
erc721SupportedHistory[nftToken] = true;
|
|
emit RemoveNFTSuppout(nftToken);
|
|
}
|
|
|
|
/**
|
|
* @dev Add 1155 NFT support
|
|
*/
|
|
function addERC1155Support(address nftToken) external onlyOwner {
|
|
erc1155Supported[nftToken] = true;
|
|
emit AddNFTSuppout(nftToken);
|
|
}
|
|
|
|
/**
|
|
* @dev Remove 1155 NFT support
|
|
*/
|
|
function removeERC1155Support(address nftToken) external onlyOwner {
|
|
erc1155Supported[nftToken] = false;
|
|
erc1155SupportedHistory[nftToken] = true;
|
|
emit RemoveNFTSuppout(nftToken);
|
|
}
|
|
|
|
/**
|
|
* @dev Add ERC20 support
|
|
*/
|
|
function addERC20Support(address erc20) external onlyOwner {
|
|
require(erc20 != address(0), "NFTMarket: ERC20 address is zero");
|
|
erc20Supported[erc20] = true;
|
|
emit AddERC20Suppout(erc20);
|
|
}
|
|
|
|
/**
|
|
* @dev Remove ERC20 support
|
|
*/
|
|
function removeERC20Support(address erc20) external onlyOwner {
|
|
erc20Supported[erc20] = false;
|
|
emit RemoveERC20Suppout(erc20);
|
|
}
|
|
|
|
/**
|
|
* @dev Set the maximum price limit for NFT
|
|
*/
|
|
function setNFTPriceMaxLimit(
|
|
address nftToken,
|
|
uint256 maxLimit
|
|
) external onlyOwner {
|
|
require(
|
|
maxLimit >= nftPriceMinLimit[nftToken],
|
|
"NFTMarket: maxLimit can not be less than min limit!"
|
|
);
|
|
nftPriceMaxLimit[nftToken] = maxLimit;
|
|
}
|
|
|
|
/**
|
|
* @dev Set the minimum price limit for NFT
|
|
*/
|
|
function setNFTPriceMinLimit(
|
|
address nftToken,
|
|
uint256 minLimit
|
|
) external onlyOwner {
|
|
if (nftPriceMaxLimit[nftToken] != 0) {
|
|
require(
|
|
minLimit <= nftPriceMaxLimit[nftToken],
|
|
"NFTMarket: minLimit can not be larger than max limit!"
|
|
);
|
|
}
|
|
nftPriceMinLimit[nftToken] = minLimit;
|
|
}
|
|
|
|
/**
|
|
* @dev Set the transaction fee
|
|
*/
|
|
function setTransactionFee(uint256 _transactionFee) external onlyOwner {
|
|
require(
|
|
_transactionFee >= MIN_TRANSACTION_FEE &&
|
|
_transactionFee <= MAX_TRANSACTION_FEE,
|
|
"NFTMarket: _transactionFee must >= 0 and <= 10%"
|
|
);
|
|
transactionFee = _transactionFee;
|
|
}
|
|
|
|
/**
|
|
* @dev Set the fee received address
|
|
*/
|
|
function setFeeToAddress(address _feeToAddress) external onlyOwner {
|
|
require(
|
|
_feeToAddress != address(0),
|
|
"NFTMarket: fee received address can not be zero"
|
|
);
|
|
feeToAddress = _feeToAddress;
|
|
}
|
|
|
|
/**
|
|
* @dev Set the transaction tax
|
|
*/
|
|
function setTransactionTax(uint256 _transactionTax) external onlyOwner {
|
|
require(
|
|
_transactionTax >= MIN_TRANSACTION_TAX &&
|
|
_transactionTax <= MAX_TRANSACTION_TAX,
|
|
"NFTMarket: _transactionTax must >= 0 and <= 10%"
|
|
);
|
|
transactionTax = _transactionTax;
|
|
}
|
|
|
|
/**
|
|
* @dev Set the tax received address
|
|
*/
|
|
function setTaxToAddress(address _taxToAddress) external onlyOwner {
|
|
require(
|
|
_taxToAddress != address(0),
|
|
"NFTMarket: tax received address can not be zero"
|
|
);
|
|
taxToAddress = _taxToAddress;
|
|
}
|
|
}
|