// SPDX-License-Identifier: MIT pragma solidity 0.8.10; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/SafeMath.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/cryptography/ECDSA.sol"; import "../core/HasSignature.sol"; // this contract will transfer ownership to BETimelockController after deployed // all onlyowner method would add timelock contract MarketPlace is Ownable, HasSignature { using SafeERC20 for IERC20; using SafeMath for uint256; // Supported payment token WETH & list of authorized ERC20 mapping(address => bool) public paymentTokens; mapping(bytes => bool) public usedSignatures; // Address to receive transaction fee address public feeToAddress; uint256 public transactionFee; uint256 public constant MIN_TRANSACTION_FEE = 50; uint256 public constant MAX_TRANSACTION_FEE = 1000; // Events event MatchTransaction( uint256 indexed tokenId, address contractAddress, uint256 price, address paymentToken, address seller, address buyer, uint256 fee ); constructor() HasSignature("MarketPlace", "1") {} function setFeeToAddress(address _feeToAddress) external onlyOwner { require( _feeToAddress != address(0), "fee received address can not be zero" ); feeToAddress = _feeToAddress; } function setTransactionFee(uint256 _transactionFee) external onlyOwner { require( _transactionFee >= MIN_TRANSACTION_FEE && _transactionFee <= MAX_TRANSACTION_FEE, "Marketplace: _transactionFee must >= 50 and <= 1000" ); transactionFee = _transactionFee; } 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 ignoreSignature( address[2] calldata addresses, uint256[3] calldata values, bytes calldata signature ) external signatureValid(signature) { bytes32 criteriaMessageHash = getMessageHash( addresses[0], values[0], addresses[1], values[1], values[2] ); checkSigner(_msgSender(), criteriaMessageHash, signature); _useSignature(signature); } /** * @dev Function matched transaction with user signatures */ function matchTransaction( address[3] calldata addresses, uint256[3] calldata values, bytes calldata signature ) external signatureValid(signature) returns (bool) { // address[3] [seller_address,nft_address,payment_token_address] // uint256[3] [token_id,price,salt_nonce] // bytes seller_signature require(paymentTokens[addresses[2]], "Marketplace: invalid payment method"); bytes32 criteriaMessageHash = getMessageHash( addresses[1], values[0], addresses[2], values[1], values[2] ); checkSigner712(addresses[0], criteriaMessageHash, signature); // check current ownership IERC721 nft = IERC721(addresses[1]); require( nft.ownerOf(values[0]) == addresses[0], "Marketplace: seller is not owner of this item now" ); // Check payment approval and buyer balance IERC20 paymentContract = IERC20(addresses[2]); require( paymentContract.balanceOf(_msgSender()) >= values[1], "Marketplace: buyer doesn't have enough token to buy this item" ); require( paymentContract.allowance(_msgSender(), address(this)) >= values[1], "Marketplace: buyer doesn't approve marketplace to spend payment amount" ); // We divide by 10000 to support decimal value such as 4.25% => 425 / 10000 uint256 fee = transactionFee.mul(values[1]).div(10000); uint256 payToSellerAmount = values[1].sub(fee); // transfer money to seller paymentContract.safeTransferFrom( _msgSender(), addresses[0], payToSellerAmount ); // transfer fee to address if (fee > 0) { paymentContract.safeTransferFrom(_msgSender(), feeToAddress, fee); } // transfer item to buyer nft.safeTransferFrom(addresses[0], _msgSender(), values[0]); _useSignature(signature); // emit sale event emitEvent(addresses, values); return true; } /** * @dev Function to emit transaction matched event */ function emitEvent(address[3] calldata addresses, uint256[3] calldata values) internal { emit MatchTransaction( values[0], addresses[1], values[1], addresses[2], addresses[0], _msgSender(), transactionFee ); } function getMessageHash( address _nftAddress, uint256 _tokenId, address _paymentErc20, uint256 _price, uint256 _saltNonce ) public pure returns (bytes32) { return keccak256( abi.encode( keccak256( "set(address nft,uint256 tokenId,address payToken,uint256 price,uint256 salt)" ), _nftAddress, _tokenId, _paymentErc20, _price, _saltNonce ) ); } }