// SPDX-License-Identifier: MIT pragma solidity 0.8.10; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/governance/TimelockController.sol"; import "../core/HasSignature.sol"; import "../interfaces/IBEERC721.sol"; import "../interfaces/IBEERC1155.sol"; import "../utils/TimeChecker.sol"; contract BENftMall is Ownable, HasSignature, TimeChecker { using SafeERC20 for IERC20; constructor() HasSignature("NftMall", "1") {} // Supported payment token WETH & list of authorized ERC20 mapping(address => bool) public paymentTokens; mapping(bytes => bool) public usedSignatures; mapping(address => bool) public nftTokenSupported; // Address to receive transaction fee address public feeToAddress; // Events event BuyTransactionBatch( address indexed buyer, uint256 indexed nonce, address[3] addresses, uint256 price, uint256[] ids, uint256[] amounts ); event BuyTransaction( address indexed buyer, uint256 indexed nonce, uint256 tokenId, address[3] addresses, uint256 price ); function setFeeToAddress(address _feeToAddress) external onlyOwner { require( _feeToAddress != address(0), "fee received address can not be zero" ); feeToAddress = _feeToAddress; } function setPaymentTokens(address[] calldata _paymentTokens) external onlyOwner { for (uint256 i = 0; i < _paymentTokens.length; i++) { if (paymentTokens[_paymentTokens[i]]) { continue; } paymentTokens[_paymentTokens[i]] = true; } } function removePaymentTokens(address[] calldata _removedPaymentTokens) external onlyOwner { for (uint256 i = 0; i < _removedPaymentTokens.length; i++) { paymentTokens[_removedPaymentTokens[i]] = false; } } function addNFTTokenSupport(address nftToken) external onlyOwner { nftTokenSupported[nftToken] = true; } function removeNFTTokenSupport(address nftToken) external onlyOwner { nftTokenSupported[nftToken] = false; } function ignoreSignature( address[4] calldata addresses, uint256[] calldata signArray, bytes calldata signature ) external signatureValid(signature) { // address[4] [seller_address,nft_address,payment_token_address, buyer_address] // uint256[4] [token_id,price,salt_nonce,startTime] bytes32 criteriaMessageHash = getMessageHash( addresses[1], addresses[2], addresses[3], signArray ); checkSigner(_msgSender(), criteriaMessageHash, signature); _useSignature(signature); } /** * @dev Function matched transaction with user signatures */ function buy721NFT( address[3] calldata addresses, uint256[4] calldata values, bytes calldata signature ) external signatureValid(signature) timeValid(values[3]) { // address[3] [seller_address,nft_address,payment_token_address] // uint256[4] [token_id,price,salt_nonce,startTime] // bytes seller_signature require(nftTokenSupported[addresses[1]], "BENftMall: Unsupported NFT"); require(paymentTokens[addresses[2]], "BENftMall: invalid payment method"); address to = _msgSender(); uint256[] memory signArray = new uint256[](values.length); for (uint256 i = 0; i < values.length; ++i) { signArray[i] = values[i]; } bytes32 criteriaMessageHash = getMessageHash( addresses[1], addresses[2], to, signArray ); checkSigner(addresses[0], criteriaMessageHash, signature); // Check payment approval and buyer balance IERC20 paymentContract = IERC20(addresses[2]); require( paymentContract.balanceOf(to) >= values[1], "BENftMall: buyer doesn't have enough token to buy this item" ); require( paymentContract.allowance(to, address(this)) >= values[1], "BENftMall: buyer doesn't approve marketplace to spend payment amount" ); paymentContract.safeTransferFrom(to, feeToAddress, values[1]); // mint item to user IBEERC721 nft = IBEERC721(addresses[1]); nft.mint(to, values[0]); _useSignature(signature); // emit sale event emit BuyTransaction(to, values[2], values[0], addresses, values[1]); } function buy1155NFT( address[3] calldata addresses, uint256[3] calldata values, uint256[] memory ids, uint256[] memory amounts, bytes calldata signature ) external signatureValid(signature) timeValid(values[2]) { // address[3] [seller_address,nft_address,payment_token_address] // uint256[3] [price,salt_nonce,startTime] require(nftTokenSupported[addresses[1]], "BENftMall: Unsupported NFT"); require(paymentTokens[addresses[2]], "BENftMall: invalid payment method"); require(ids.length > 0, "BENftMall: ids cannot be empty"); require( ids.length == amounts.length, "BENftMall: ids and amounts length mismatch" ); IBEERC1155 nft = IBEERC1155(addresses[1]); uint256[] memory signArray = new uint256[](ids.length * 2 + values.length); for (uint256 i = 0; i < ids.length; ++i) { require( nft.canMint(ids[i]), "BENftMall: can not mint for current nft rule setting" ); signArray[i * 2] = ids[i]; signArray[i * 2 + 1] = amounts[i]; } for (uint256 i = 0; i < values.length; ++i) { signArray[ids.length * 2 + i] = values[i]; } bytes32 criteriaMessageHash = getMessageHash( addresses[1], addresses[2], _msgSender(), signArray ); checkSigner(addresses[0], criteriaMessageHash, signature); // Check payment approval and buyer balance IERC20 paymentContract = IERC20(addresses[2]); require( paymentContract.balanceOf(_msgSender()) >= values[0], "BENftMall: buyer doesn't have enough token to buy this item" ); require( paymentContract.allowance(_msgSender(), address(this)) >= values[0], "BENftMall: buyer doesn't approve marketplace to spend payment amount" ); paymentContract.safeTransferFrom(_msgSender(), feeToAddress, values[0]); nft.mintBatch(_msgSender(), ids, amounts, ""); _useSignature(signature); emit BuyTransactionBatch(_msgSender(), values[1], addresses, values[0], ids, amounts); } function getMessageHash( address _nftAddress, address _tokenAddress, address _buyerAddress, uint256[] memory _datas ) public pure returns (bytes32) { bytes memory encoded = abi.encodePacked( _nftAddress, _tokenAddress, _buyerAddress ); uint256 len = _datas.length; for (uint256 i = 0; i < len; ++i) { encoded = bytes.concat(encoded, abi.encodePacked(_datas[i])); } return keccak256(encoded); } }