├── README.md ├── interfaces ├── IERC20Approve.sol ├── ITokenCreator.sol ├── IERC20IncreaseAllowance.sol ├── IOwnable.sol ├── IProxyCall.sol ├── IGetRoyalties.sol ├── ICollectionContractInitializer.sol ├── ICollectionFactory.sol ├── IOperatorRole.sol ├── IAdminRole.sol ├── IRoles.sol ├── ens │ ├── IPublicResolver.sol │ ├── IReverseRegistrar.sol │ └── IENS.sol ├── IGetFees.sol ├── IRoyaltyInfo.sol ├── IMethMarket.sol └── IMarketDeprecatedAPIs.sol ├── mocks ├── EmptyMockContract.sol ├── MockNFT.sol └── MarketDeprecatedEvents.sol ├── mixins ├── ERC165UpgradeableGap.sol ├── NFT721Core.sol ├── NFTMarketPrivateSaleGap.sol ├── Constants.sol ├── NFTMarketAuction.sol ├── CollateralManagement.sol ├── roles │ ├── OperatorRole.sol │ └── AdminRole.sol ├── MuseeTreasuryNode.sol ├── NFT721ProxyCall.sol ├── SendValueWithFallbackWithdraw.sol ├── NFT721Metadata.sol ├── OZ │ ├── ERC165Checker.sol │ ├── AccessControlUpgradeable.sol │ ├── EnumerableMap.sol │ └── ERC721Upgradeable.sol ├── NFT721Market.sol ├── NFTMarketCore.sol ├── NFT721Mint.sol ├── NFTMarketFees.sol ├── NFTMarketCreators.sol ├── NFTMarketPrivateSale.sol ├── NFT721Creator.sol ├── NFTMarketBuyPrice.sol └── NFTMarketOffer.sol ├── MuseeTreasury.sol ├── libraries ├── ProxyCall.sol ├── BytesLibrary.sol ├── AccountMigrationLibrary.sol └── LockedBalance.sol ├── ProxyCall.sol ├── ExternalProxyCall.sol ├── MUSEENFT721.sol ├── MUSEENFTMarket.sol ├── MUSEECollectionFactory.sol └── PercentSplitETH.sol /README.md: -------------------------------------------------------------------------------- 1 | Marketplace Contract 2 | 3 | Musee Art NFT Marketplace (working similar to Foundation) 4 | -------------------------------------------------------------------------------- /interfaces/IERC20Approve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IERC20Approve { 6 | function approve(address spender, uint256 amount) external returns (bool); 7 | } 8 | -------------------------------------------------------------------------------- /interfaces/ITokenCreator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface ITokenCreator { 6 | function tokenCreator(uint256 tokenId) external view returns (address payable); 7 | } 8 | -------------------------------------------------------------------------------- /mocks/EmptyMockContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract EmptyMockContract { 6 | // Something must be included in order to generate the typechain file 7 | event DummyEvent(); 8 | } 9 | -------------------------------------------------------------------------------- /interfaces/IERC20IncreaseAllowance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IERC20IncreaseAllowance { 6 | function increaseAllowance(address spender, uint256 addedValue) external returns (bool); 7 | } 8 | -------------------------------------------------------------------------------- /interfaces/IOwnable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IOwnable { 6 | /** 7 | * @dev Returns the address of the current owner. 8 | */ 9 | function owner() external view returns (address); 10 | } 11 | -------------------------------------------------------------------------------- /interfaces/IProxyCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IProxyCall { 6 | function proxyCallAndReturnAddress(address externalContract, bytes memory callData) 7 | external 8 | returns (address payable result); 9 | } 10 | -------------------------------------------------------------------------------- /interfaces/IGetRoyalties.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IGetRoyalties { 6 | function getRoyalties(uint256 tokenId) 7 | external 8 | view 9 | returns (address payable[] memory recipients, uint256[] memory feesInBasisPoints); 10 | } 11 | -------------------------------------------------------------------------------- /interfaces/ICollectionContractInitializer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface ICollectionContractInitializer { 6 | function initialize( 7 | address payable _creator, 8 | string memory _name, 9 | string memory _symbol 10 | ) external; 11 | } 12 | -------------------------------------------------------------------------------- /interfaces/ICollectionFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./IRoles.sol"; 6 | import "./IProxyCall.sol"; 7 | 8 | interface ICollectionFactory { 9 | function rolesContract() external returns (IRoles); 10 | 11 | function proxyCallContract() external returns (IProxyCall); 12 | } 13 | -------------------------------------------------------------------------------- /interfaces/IOperatorRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @notice Interface for OperatorRole which wraps a role from 7 | * OpenZeppelin's AccessControl for easy integration. 8 | */ 9 | interface IOperatorRole { 10 | function isOperator(address account) external view returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /interfaces/IAdminRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @notice Interface for AdminRole which wraps the default admin role from 7 | * OpenZeppelin's AccessControl for easy integration. 8 | */ 9 | interface IAdminRole { 10 | function isAdmin(address account) external view returns (bool); 11 | } 12 | -------------------------------------------------------------------------------- /interfaces/IRoles.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @notice Interface for a contract which implements admin roles. 7 | */ 8 | interface IRoles { 9 | function isAdmin(address account) external view returns (bool); 10 | 11 | function isOperator(address account) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /interfaces/ens/IPublicResolver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title Interface for the main ENS resolver contract. 7 | */ 8 | interface IPublicResolver { 9 | function setAddr(bytes32 node, address a) external; 10 | 11 | function name(bytes32 node) external view returns (string memory); 12 | } 13 | -------------------------------------------------------------------------------- /interfaces/ens/IReverseRegistrar.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title Interface for the main ENS reverse registrar contract. 7 | */ 8 | interface IReverseRegistrar { 9 | function setName(string memory name) external; 10 | 11 | function node(address addr) external pure returns (bytes32); 12 | } 13 | -------------------------------------------------------------------------------- /mixins/ERC165UpgradeableGap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title A gap to represent the space previously consumed by the use of ERC165Upgradeable. 7 | */ 8 | abstract contract ERC165UpgradeableGap { 9 | /// @notice The size of the ERC165Upgradeable contract which is no longer used. 10 | uint256[50] private __gap; 11 | } 12 | -------------------------------------------------------------------------------- /interfaces/IGetFees.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @notice An interface for communicating fees to 3rd party marketplaces. 7 | * @dev Originally implemented in mainnet contract 0x44d6e8933f8271abcf253c72f9ed7e0e4c0323b3 8 | */ 9 | interface IGetFees { 10 | function getFeeRecipients(uint256 id) external view returns (address payable[] memory); 11 | 12 | function getFeeBps(uint256 id) external view returns (uint256[] memory); 13 | } 14 | -------------------------------------------------------------------------------- /mocks/MockNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 7 | 8 | contract MockNFT is Ownable, ERC721 { 9 | uint256 private nextTokenId; 10 | 11 | constructor() 12 | ERC721("MockNFT", "mNFT") // solhint-disable-next-line no-empty-blocks 13 | {} 14 | 15 | function mint() external onlyOwner { 16 | _mint(msg.sender, ++nextTokenId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /interfaces/ens/IENS.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title Interface for the main ENS contract 7 | */ 8 | interface IENS { 9 | function setSubnodeOwner( 10 | bytes32 node, 11 | bytes32 label, 12 | address owner 13 | ) external returns (bytes32); 14 | 15 | function setResolver(bytes32 node, address resolver) external; 16 | 17 | function owner(bytes32 node) external view returns (address); 18 | 19 | function resolver(bytes32 node) external view returns (address); 20 | } 21 | -------------------------------------------------------------------------------- /mixins/NFT721Core.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title A place for common modifiers and functions used by various NFT721 mixins, if any. 7 | * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree. 8 | */ 9 | abstract contract NFT721Core { 10 | /** 11 | * @notice This empty reserved space is put in place to allow future versions to add new 12 | * variables without shifting down storage in the inheritance chain. 13 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 14 | * @dev 100 slots used when adding `NFT721ProxyCall`. 15 | */ 16 | uint256[900] private __gap; 17 | } 18 | -------------------------------------------------------------------------------- /interfaces/IRoyaltyInfo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @notice Interface for EIP-2981: NFT Royalty Standard. 7 | * For more see: https://eips.ethereum.org/EIPS/eip-2981. 8 | */ 9 | interface IRoyaltyInfo { 10 | /// @notice Called with the sale price to determine how much royalty 11 | // is owed and to whom. 12 | /// @param _tokenId - the NFT asset queried for royalty information 13 | /// @param _salePrice - the sale price of the NFT asset specified by _tokenId 14 | /// @return receiver - address of who should be sent the royalty payment 15 | /// @return royaltyAmount - the royalty payment amount for _salePrice 16 | function royaltyInfo(uint256 _tokenId, uint256 _salePrice) 17 | external 18 | view 19 | returns (address receiver, uint256 royaltyAmount); 20 | } 21 | -------------------------------------------------------------------------------- /MuseeTreasury.sol: -------------------------------------------------------------------------------- 1 | /* 2 | MUSEE Protocol 3 | */ 4 | 5 | // SPDX-License-Identifier: MIT OR Apache-2.0 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | import "./mixins/roles/AdminRole.sol"; 10 | import "./mixins/roles/OperatorRole.sol"; 11 | import "./mixins/CollateralManagement.sol"; 12 | 13 | /** 14 | * @title Manage revenue and roles for Musee. 15 | * @notice All fees generated by the market are forwarded to this contract. 16 | * It also defines the Admin and Operator roles which are used in other contracts. 17 | */ 18 | contract MuseeTreasury is AdminRole, OperatorRole, CollateralManagement { 19 | /** 20 | * @notice Called one time after deployment to initialize the contract. 21 | * @param admin The account to add as the initial admin. 22 | */ 23 | function initialize(address admin) external initializer { 24 | AdminRole._initializeAdminRole(admin); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mixins/NFTMarketPrivateSaleGap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title Reserves space previously occupied by private sales. 7 | */ 8 | abstract contract NFTMarketPrivateSaleGap { 9 | // Original data: 10 | // bytes32 private __gap_was_DOMAIN_SEPARATOR; 11 | // mapping(address => mapping(uint256 => mapping(address => mapping(address => mapping(uint256 => 12 | // mapping(uint256 => bool)))))) private privateSaleInvalidated; 13 | // uint256[999] private __gap; 14 | 15 | /** 16 | * @notice This empty reserved space is put in place to allow future versions to add new 17 | * variables without shifting down storage in the inheritance chain. 18 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 19 | * @dev 1 slot was consumed by privateSaleInvalidated. 20 | */ 21 | uint256[1001] private __gap; 22 | } 23 | -------------------------------------------------------------------------------- /interfaces/IMethMarket.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @notice Interface for functions the market uses in METH. 7 | */ 8 | interface IMethMarket { 9 | function depositFor(address account) external payable; 10 | 11 | function marketLockupFor(address account, uint256 amount) external payable returns (uint256 expiration); 12 | 13 | function marketWithdrawFrom(address from, uint256 amount) external; 14 | 15 | function marketWithdrawLocked( 16 | address account, 17 | uint256 expiration, 18 | uint256 amount 19 | ) external; 20 | 21 | function marketUnlockFor( 22 | address account, 23 | uint256 expiration, 24 | uint256 amount 25 | ) external; 26 | 27 | function marketChangeLockup( 28 | address unlockFrom, 29 | uint256 unlockExpiration, 30 | uint256 unlockAmount, 31 | address depositFor, 32 | uint256 depositAmount 33 | ) external payable returns (uint256 expiration); 34 | } 35 | -------------------------------------------------------------------------------- /libraries/ProxyCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | import "../interfaces/IProxyCall.sol"; 7 | 8 | /** 9 | * @title A library which forwards arbitrary calls to an external contract to be processed. 10 | * @dev This is used so that the from address of the calling contract does not have 11 | * any special permissions (e.g. ERC-20 transfer). 12 | */ 13 | library ProxyCall { 14 | using AddressUpgradeable for address payable; 15 | 16 | /** 17 | * @dev Used by other mixins to make external calls through the proxy contract. 18 | * This will fail if the proxyCall address is address(0). 19 | */ 20 | function proxyCallAndReturnContractAddress( 21 | IProxyCall proxyCall, 22 | address externalContract, 23 | bytes memory callData 24 | ) internal returns (address payable result) { 25 | result = proxyCall.proxyCallAndReturnAddress(externalContract, callData); 26 | require(result.isContract(), "ProxyCall: address returned is not a contract"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ProxyCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/utils/Address.sol"; 6 | import "./interfaces/IProxyCall.sol"; 7 | 8 | /** 9 | * @notice Forwards arbitrary calls to an external contract. 10 | * @dev This is used so that the from address of the calling contract does not have 11 | * any special permissions (e.g. ERC-20 transfer). 12 | * Other return types and call structures may be added in the future. 13 | * 14 | * DO NOT approve this contract to transfer any ERC-20 or ERC-721, or grant any other permissions for another contract. 15 | */ 16 | contract ProxyCall is IProxyCall { 17 | using Address for address; 18 | 19 | function proxyCallAndReturnAddress(address externalContract, bytes calldata callData) 20 | external 21 | override 22 | returns (address payable result) 23 | { 24 | bytes memory returnData = externalContract.functionCall(callData); 25 | 26 | // Skip the length at the start of the bytes array and return the data, casted to an address 27 | // solhint-disable-next-line no-inline-assembly 28 | assembly { 29 | result := mload(add(returnData, 32)) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /interfaces/IMarketDeprecatedAPIs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @dev Historical (currently deprecated) APIs 7 | * that the subgraph depends on for historical transactions. 8 | */ 9 | interface IMarketDeprecatedAPIs { 10 | /** 11 | * @notice Emitted when the seller for an auction has been changed to a new account. 12 | * @dev Account migrations require approval from both the original account and Musee. 13 | * @param auctionId The id of the auction that was updated. 14 | * @param originalSellerAddress The original address of the auction's seller. 15 | * @param newSellerAddress The new address for the auction's seller. 16 | */ 17 | event ReserveAuctionSellerMigrated( 18 | uint256 indexed auctionId, 19 | address indexed originalSellerAddress, 20 | address indexed newSellerAddress 21 | ); 22 | 23 | function getIsPrimary(address nftContract, uint256 tokenId) external view returns (bool isPrimary); 24 | 25 | function getFees( 26 | address nftContract, 27 | uint256 tokenId, 28 | uint256 price 29 | ) 30 | external 31 | view 32 | returns ( 33 | uint256 museeFee, 34 | uint256 creatorRev, 35 | uint256 ownerRev 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /ExternalProxyCall.sol: -------------------------------------------------------------------------------- 1 | /* 2 | MUSEE Protocol 3 | */ 4 | 5 | // SPDX-License-Identifier: MIT OR Apache-2.0 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 10 | import "./interfaces/IProxyCall.sol"; 11 | 12 | /** 13 | * @title Forwards arbitrary calls to an external contract. 14 | * @notice DO NOT approve this contract to transfer any ERC-20 or ERC-721, 15 | * or grant any other permissions for another contract. 16 | * @dev This is used so that the from address of the calling contract does not have 17 | * any special permissions (e.g. ERC-20 transfer). 18 | * Other return types and call structures may be added in the future. 19 | */ 20 | contract ExternalProxyCall is IProxyCall { 21 | using AddressUpgradeable for address; 22 | 23 | function proxyCallAndReturnAddress(address externalContract, bytes memory callData) 24 | external 25 | override 26 | returns (address payable result) 27 | { 28 | bytes memory returnData = externalContract.functionCall(callData); 29 | 30 | // Skip the length at the start of the bytes array and return the data, casted to an address 31 | // solhint-disable-next-line no-inline-assembly 32 | assembly { 33 | result := mload(add(returnData, 32)) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mixins/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title Constant values shared across mixins. 7 | */ 8 | abstract contract Constants { 9 | /** 10 | * @notice 100% in basis points. 11 | */ 12 | uint256 internal constant BASIS_POINTS = 10000; 13 | 14 | /** 15 | * @notice Cap the number of royalty recipients to 5. 16 | * @dev A cap is required to ensure gas costs are not too high when a sale is settled. 17 | */ 18 | uint256 internal constant MAX_ROYALTY_RECIPIENTS_INDEX = 4; 19 | 20 | /** 21 | * @notice The minimum increase of 10% required when making an offer or placing a bid. 22 | */ 23 | uint256 internal constant MIN_PERCENT_INCREMENT_DENOMINATOR = BASIS_POINTS / 1000; 24 | 25 | /** 26 | * @notice The gas limit used when making external read-only calls. 27 | * @dev This helps to ensure that external calls does not prevent the market from executing. 28 | */ 29 | uint256 internal constant READ_ONLY_GAS_LIMIT = 40000; 30 | 31 | /** 32 | * @notice The gas limit to send ETH to multiple recipients, enough for a 5-way split. 33 | */ 34 | uint256 internal constant SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS = 210000; 35 | 36 | /** 37 | * @notice The gas limit to send ETH to a single recipient, enough for a contract with a simple receiver. 38 | */ 39 | uint256 internal constant SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT = 20000; 40 | } 41 | -------------------------------------------------------------------------------- /mixins/NFTMarketAuction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | 7 | /** 8 | * @title An abstraction layer for auctions. 9 | * @dev This contract can be expanded with reusable calls and data as more auction types are added. 10 | */ 11 | abstract contract NFTMarketAuction is Initializable { 12 | /** 13 | * @notice A global id for auctions of any type. 14 | */ 15 | uint256 private nextAuctionId; 16 | 17 | /** 18 | * @notice Called once to configure the contract after the initial proxy deployment. 19 | * @dev This sets the initial auction id to 1, making the first auction cheaper 20 | * and id 0 represents no auction found. 21 | */ 22 | function _initializeNFTMarketAuction() internal onlyInitializing { 23 | nextAuctionId = 1; 24 | } 25 | 26 | /** 27 | * @notice Returns id to assign to the next auction. 28 | */ 29 | function _getNextAndIncrementAuctionId() internal returns (uint256) { 30 | // AuctionId cannot overflow 256 bits. 31 | unchecked { 32 | return nextAuctionId++; 33 | } 34 | } 35 | 36 | /** 37 | * @notice This empty reserved space is put in place to allow future versions to add new 38 | * variables without shifting down storage in the inheritance chain. 39 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 40 | */ 41 | uint256[1000] private __gap; 42 | } 43 | -------------------------------------------------------------------------------- /libraries/BytesLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | error BytesLibrary_Expected_Address_Not_Found(); 6 | 7 | /** 8 | * @title A library for manipulation of byte arrays. 9 | */ 10 | library BytesLibrary { 11 | /** 12 | * @dev Replace the address at the given location in a byte array if the contents at that location 13 | * match the expected address. 14 | */ 15 | function replaceAtIf( 16 | bytes memory data, 17 | uint256 startLocation, 18 | address expectedAddress, 19 | address newAddress 20 | ) internal pure { 21 | bytes memory expectedData = abi.encodePacked(expectedAddress); 22 | bytes memory newData = abi.encodePacked(newAddress); 23 | unchecked { 24 | // An address is 20 bytes long 25 | for (uint256 i = 0; i < 20; ++i) { 26 | uint256 dataLocation = startLocation + i; 27 | if (data[dataLocation] != expectedData[i]) { 28 | revert BytesLibrary_Expected_Address_Not_Found(); 29 | } 30 | data[dataLocation] = newData[i]; 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * @dev Checks if the call data starts with the given function signature. 37 | */ 38 | function startsWith(bytes memory callData, bytes4 functionSig) internal pure returns (bool) { 39 | // A signature is 4 bytes long 40 | if (callData.length < 4) { 41 | return false; 42 | } 43 | unchecked { 44 | for (uint256 i = 0; i < 4; ++i) { 45 | if (callData[i] != functionSig[i]) { 46 | return false; 47 | } 48 | } 49 | } 50 | 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mocks/MarketDeprecatedEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @dev Historical (currently deprecated) events 7 | * that the subgraph depends on for historical transactions. 8 | */ 9 | contract MarketDeprecatedEvents { 10 | // From NFTMarketReserveAuction 11 | /** 12 | * @notice Emitted when the seller for an auction has been changed to a new account. 13 | * @dev Account migrations require approval from both the original account and Musee. 14 | * @param auctionId The id of the auction that was updated. 15 | * @param originalSellerAddress The original address of the auction's seller. 16 | * @param newSellerAddress The new address for the auction's seller. 17 | */ 18 | event ReserveAuctionSellerMigrated( 19 | uint256 indexed auctionId, 20 | address indexed originalSellerAddress, 21 | address indexed newSellerAddress, 22 | address indexed newSellerAddress2 23 | ); 24 | 25 | // From SendValueWithFallbackWithdraw 26 | /** 27 | * @notice [DEPRECATED] Emitted when an attempt to send ETH fails 28 | * or runs out of gas and the value is stored in escrow instead. 29 | * @param user The account which has escrowed ETH to withdraw. 30 | * @param amount The amount of ETH which has been added to the user's escrow balance. 31 | */ 32 | event WithdrawPending(address indexed user, uint256 amount); 33 | /** 34 | * @notice [DEPRECATED] Emitted when escrowed funds are withdrawn. 35 | * @param user The account which has withdrawn ETH. 36 | * @param amount The amount of ETH which has been withdrawn. 37 | */ 38 | event Withdrawal(address indexed user, uint256 amount); 39 | } 40 | -------------------------------------------------------------------------------- /mixins/CollateralManagement.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | import "../mixins/roles/AdminRole.sol"; 8 | 9 | error CollateralManagement_Cannot_Withdraw_To_Address_Zero(); 10 | error CollateralManagement_Cannot_Withdraw_To_Self(); 11 | 12 | /** 13 | * @title Enables deposits and withdrawals. 14 | */ 15 | abstract contract CollateralManagement is AdminRole { 16 | using AddressUpgradeable for address payable; 17 | 18 | /** 19 | * @notice Emitted when funds are withdrawn from this contract. 20 | * @param to The address which received the ETH withdrawn. 21 | * @param amount The amount of ETH which was withdrawn. 22 | */ 23 | event FundsWithdrawn(address indexed to, uint256 amount); 24 | 25 | /** 26 | * @notice Accept native currency payments (i.e. fees) 27 | */ 28 | // solhint-disable-next-line no-empty-blocks 29 | receive() external payable {} 30 | 31 | /** 32 | * @notice Allows an admin to withdraw funds. 33 | * @param to Address to receive the withdrawn funds 34 | * @param amount Amount to withdrawal or 0 to withdraw all available funds 35 | */ 36 | function withdrawFunds(address payable to, uint256 amount) external onlyAdmin { 37 | if (amount == 0) { 38 | amount = address(this).balance; 39 | } 40 | if (to == address(0)) { 41 | revert CollateralManagement_Cannot_Withdraw_To_Address_Zero(); 42 | } else if (to == address(this)) { 43 | revert CollateralManagement_Cannot_Withdraw_To_Self(); 44 | } 45 | to.sendValue(amount); 46 | 47 | emit FundsWithdrawn(to, amount); 48 | } 49 | 50 | /** 51 | * @notice This empty reserved space is put in place to allow future versions to add new 52 | * variables without shifting down storage in the inheritance chain. 53 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 54 | */ 55 | uint256[1000] private __gap; 56 | } 57 | -------------------------------------------------------------------------------- /mixins/roles/OperatorRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../OZ/AccessControlUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 7 | 8 | /** 9 | * @title Defines a role for Musee operator accounts. 10 | * @dev Wraps a role from OpenZeppelin's AccessControl for easy integration. 11 | */ 12 | abstract contract OperatorRole is Initializable, AccessControlUpgradeable { 13 | bytes32 private constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); 14 | 15 | /** 16 | * @notice Adds the account to the list of approved operators. 17 | * @dev Only callable by admins as enforced by `grantRole`. 18 | * @param account The address to be approved. 19 | */ 20 | function grantOperator(address account) external { 21 | grantRole(OPERATOR_ROLE, account); 22 | } 23 | 24 | /** 25 | * @notice Removes the account from the list of approved operators. 26 | * @dev Only callable by admins as enforced by `revokeRole`. 27 | * @param account The address to be removed from the approved list. 28 | */ 29 | function revokeOperator(address account) external { 30 | revokeRole(OPERATOR_ROLE, account); 31 | } 32 | 33 | /** 34 | * @notice Returns one of the operator by index. 35 | * @param index The index of the operator to return from 0 to getOperatorMemberCount() - 1. 36 | * @return account The address of the operator. 37 | */ 38 | function getOperatorMember(uint256 index) external view returns (address account) { 39 | account = getRoleMember(OPERATOR_ROLE, index); 40 | } 41 | 42 | /** 43 | * @notice Checks how many accounts have been granted operator access. 44 | * @return count The number of accounts with operator access. 45 | */ 46 | function getOperatorMemberCount() external view returns (uint256 count) { 47 | count = getRoleMemberCount(OPERATOR_ROLE); 48 | } 49 | 50 | /** 51 | * @notice Checks if the account provided is an operator. 52 | * @param account The address to check. 53 | * @return approved True if the account is an operator. 54 | */ 55 | function isOperator(address account) external view returns (bool approved) { 56 | approved = hasRole(OPERATOR_ROLE, account); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /mixins/MuseeTreasuryNode.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 7 | 8 | import "../interfaces/IAdminRole.sol"; 9 | import "../interfaces/IOperatorRole.sol"; 10 | 11 | error MuseeTreasuryNode_Address_Is_Not_A_Contract(); 12 | error MuseeTreasuryNode_Caller_Not_Admin(); 13 | error MuseeTreasuryNode_Caller_Not_Operator(); 14 | 15 | /** 16 | * @title A mixin that stores a reference to the Musee treasury contract. 17 | * @notice The treasury collects fees and defines admin/operator roles. 18 | */ 19 | abstract contract MuseeTreasuryNode is Initializable { 20 | using AddressUpgradeable for address payable; 21 | 22 | /// @dev This value was replaced with an immutable version. 23 | address payable private __gap_was_treasury; 24 | 25 | /// @notice The address of the treasury contract. 26 | address payable private immutable treasury; 27 | 28 | /// @notice Requires the caller is a Musee admin. 29 | modifier onlyMuseeAdmin() { 30 | if (!IAdminRole(treasury).isAdmin(msg.sender)) { 31 | revert MuseeTreasuryNode_Caller_Not_Admin(); 32 | } 33 | _; 34 | } 35 | 36 | /// @notice Requires the caller is a Musee operator. 37 | modifier onlyMuseeOperator() { 38 | if (!IOperatorRole(treasury).isOperator(msg.sender)) { 39 | revert MuseeTreasuryNode_Caller_Not_Operator(); 40 | } 41 | _; 42 | } 43 | 44 | /** 45 | * @notice Set immutable variables for the implementation contract. 46 | * @dev Assigns the treasury contract address. 47 | */ 48 | constructor(address payable _treasury) { 49 | if (!_treasury.isContract()) { 50 | revert MuseeTreasuryNode_Address_Is_Not_A_Contract(); 51 | } 52 | treasury = _treasury; 53 | } 54 | 55 | /** 56 | * @notice Gets the Musee treasury contract. 57 | * @dev This call is used in the royalty registry contract. 58 | * @return treasuryAddress The address of the Musee treasury contract. 59 | */ 60 | function getMuseeTreasury() public view returns (address payable treasuryAddress) { 61 | return treasury; 62 | } 63 | 64 | /** 65 | * @notice This empty reserved space is put in place to allow future versions to add new 66 | * variables without shifting down storage in the inheritance chain. 67 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 68 | */ 69 | uint256[2000] private __gap; 70 | } 71 | -------------------------------------------------------------------------------- /mixins/NFT721ProxyCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | import "../interfaces/IProxyCall.sol"; 7 | 8 | /** 9 | * @title Forwards arbitrary calls to an external contract to be processed. 10 | * @dev This is used so that the from address of the calling contract does not have 11 | * any special permissions (e.g. ERC-20 transfer). 12 | */ 13 | abstract contract NFT721ProxyCall { 14 | using AddressUpgradeable for address payable; 15 | using AddressUpgradeable for address; 16 | 17 | /// @notice The address for a contract which forwards arbitrary proxy calls. 18 | /// @dev This is used to improve security, so that the msg.sender is not the NFT's address. 19 | IProxyCall private proxyCall; 20 | 21 | /** 22 | * @notice Emitted when the proxy call contract is updated. 23 | * @param proxyCallContract The new proxy call contract address. 24 | */ 25 | event ProxyCallContractUpdated(address indexed proxyCallContract); 26 | 27 | /** 28 | * @dev Used by other mixins to make external calls through the proxy contract. 29 | * This will fail if the proxyCall address is address(0). 30 | */ 31 | function _proxyCallAndReturnContractAddress(address externalContract, bytes memory callData) 32 | internal 33 | returns (address payable result) 34 | { 35 | result = proxyCall.proxyCallAndReturnAddress(externalContract, callData); 36 | require(result.isContract(), "NFT721ProxyCall: address returned is not a contract"); 37 | } 38 | 39 | /** 40 | * @dev Called by the adminUpdateConfig function to set the address of the proxy call contract. 41 | */ 42 | function _updateProxyCall(address proxyCallContract) internal { 43 | require(proxyCallContract.isContract(), "NFT721ProxyCall: Proxy call address is not a contract"); 44 | proxyCall = IProxyCall(proxyCallContract); 45 | 46 | emit ProxyCallContractUpdated(proxyCallContract); 47 | } 48 | 49 | /** 50 | * @notice Returns the address of the current proxy call contract. 51 | * @return contractAddress The address of the current proxy call contract. 52 | */ 53 | function proxyCallAddress() external view returns (address contractAddress) { 54 | contractAddress = address(proxyCall); 55 | } 56 | 57 | /** 58 | * @notice This empty reserved space is put in place to allow future versions to add new 59 | * variables without shifting down storage in the inheritance chain. 60 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 61 | */ 62 | uint256[99] private __gap; 63 | } 64 | -------------------------------------------------------------------------------- /mixins/SendValueWithFallbackWithdraw.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 7 | 8 | import "./MuseeTreasuryNode.sol"; 9 | import "./NFTMarketCore.sol"; 10 | import "./NFTMarketCreators.sol"; 11 | 12 | error SendValueWithFallbackWithdraw_No_Funds_Available(); 13 | 14 | /** 15 | * @title A mixin for sending ETH with a fallback withdraw mechanism. 16 | * @notice Attempt to send ETH and if the transfer fails or runs out of gas, store the balance 17 | * in the METH token contract for future withdrawal instead. 18 | * @dev This mixin was recently switched to escrow funds in METH. 19 | * Once we have confirmed all pending balances have been withdrawn, we can remove the escrow tracking here. 20 | */ 21 | abstract contract SendValueWithFallbackWithdraw is 22 | MuseeTreasuryNode, 23 | NFTMarketCore, 24 | ReentrancyGuardUpgradeable, 25 | NFTMarketCreators 26 | { 27 | using AddressUpgradeable for address payable; 28 | 29 | /// @dev Tracks the amount of ETH that is stored in escrow for future withdrawal. 30 | mapping(address => uint256) private __gap_was_pendingWithdrawals; 31 | 32 | /** 33 | * @notice Emitted when escrowed funds are withdrawn to METH. 34 | * @param user The account which has withdrawn ETH. 35 | * @param amount The amount of ETH which has been withdrawn. 36 | */ 37 | event WithdrawalToMETH(address indexed user, uint256 amount); 38 | 39 | /** 40 | * @dev Attempt to send a user or contract ETH and 41 | * if it fails store the amount owned for later withdrawal in METH. 42 | */ 43 | function _sendValueWithFallbackWithdraw( 44 | address payable user, 45 | uint256 amount, 46 | uint256 gasLimit 47 | ) internal { 48 | if (amount == 0) { 49 | return; 50 | } 51 | // Cap the gas to prevent consuming all available gas to block a tx from completing successfully 52 | // solhint-disable-next-line avoid-low-level-calls 53 | (bool success, ) = user.call{ value: amount, gas: gasLimit }(""); 54 | if (!success) { 55 | // Store the funds that failed to send for the user in the METH token 56 | meth.depositFor{ value: amount }(user); 57 | emit WithdrawalToMETH(user, amount); 58 | } 59 | } 60 | 61 | /** 62 | * @notice This empty reserved space is put in place to allow future versions to add new 63 | * variables without shifting down storage in the inheritance chain. 64 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 65 | */ 66 | uint256[499] private __gap; 67 | } 68 | -------------------------------------------------------------------------------- /MUSEENFT721.sol: -------------------------------------------------------------------------------- 1 | /* 2 | MUSEE Protocol 3 | */ 4 | 5 | // SPDX-License-Identifier: MIT OR Apache-2.0 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 10 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 11 | import "./mixins/OZ/ERC721Upgradeable.sol"; 12 | 13 | import "./mixins/MuseeTreasuryNode.sol"; 14 | import "./mixins/NFT721Core.sol"; 15 | import "./mixins/NFT721Market.sol"; 16 | import "./mixins/NFT721Creator.sol"; 17 | import "./mixins/NFT721Metadata.sol"; 18 | import "./mixins/NFT721Mint.sol"; 19 | import "./mixins/NFT721ProxyCall.sol"; 20 | import "./mixins/ERC165UpgradeableGap.sol"; 21 | 22 | /** 23 | * @title Musee NFTs implemented using the ERC-721 standard. 24 | */ 25 | contract MUSEENFT721 is 26 | Initializable, 27 | MuseeTreasuryNode, 28 | ERC165UpgradeableGap, 29 | ERC165, 30 | ERC721Upgradeable, 31 | NFT721Core, 32 | NFT721ProxyCall, 33 | NFT721Creator, 34 | NFT721Market, 35 | NFT721Metadata, 36 | NFT721Mint 37 | { 38 | constructor(address payable treasury) 39 | MuseeTreasuryNode(treasury) // solhint-disable-next-line no-empty-blocks 40 | {} 41 | 42 | /** 43 | * @notice Called once to configure the contract after the initial deployment. 44 | * @dev This farms the initialize call out to inherited contracts as needed. 45 | */ 46 | function initialize() external initializer { 47 | ERC721Upgradeable.__ERC721_init(); 48 | NFT721Mint._initializeNFT721Mint(); 49 | } 50 | 51 | /** 52 | * @notice Allows a Musee admin to update NFT config variables. 53 | * @dev This must be called right after the initial call to `initialize`. 54 | */ 55 | function adminUpdateConfig( 56 | address _nftMarket, 57 | string calldata baseURI, 58 | address proxyCallContract 59 | ) external onlyMuseeAdmin { 60 | _updateNFTMarket(_nftMarket); 61 | _updateBaseURI(baseURI); 62 | _updateProxyCall(proxyCallContract); 63 | } 64 | 65 | /** 66 | * @dev This is a no-op, just an explicit override to address compile errors due to inheritance. 67 | */ 68 | function _burn(uint256 tokenId) internal override(ERC721Upgradeable, NFT721Creator, NFT721Metadata, NFT721Mint) { 69 | super._burn(tokenId); 70 | } 71 | 72 | function supportsInterface(bytes4 interfaceId) 73 | public 74 | view 75 | override(ERC165, NFT721Mint, ERC721Upgradeable, NFT721Creator, NFT721Market) 76 | returns (bool) 77 | { 78 | return super.supportsInterface(interfaceId); 79 | } 80 | } 81 | 82 | /*********************************** 83 | * _burn and _transfer needed * 84 | * _burn implmeneted here * 85 | * _tansfer is at main contract * 86 | ************************************/ -------------------------------------------------------------------------------- /mixins/roles/AdminRole.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../OZ/AccessControlUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 7 | 8 | /** 9 | * @title Defines a role for Musee admin accounts. 10 | * @dev Wraps the default admin role from OpenZeppelin's AccessControl for easy integration. 11 | */ 12 | abstract contract AdminRole is Initializable, AccessControlUpgradeable { 13 | function _initializeAdminRole(address admin) internal onlyInitializing { 14 | AccessControlUpgradeable.__AccessControl_init(); 15 | // Grant the role to a specified account 16 | _setupRole(DEFAULT_ADMIN_ROLE, admin); 17 | } 18 | 19 | modifier onlyAdmin() { 20 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "AdminRole: caller does not have the Admin role"); 21 | _; 22 | } 23 | 24 | /** 25 | * @notice Adds the account to the list of approved admins. 26 | * @dev Only callable by admins as enforced by `grantRole`. 27 | * @param account The address to be approved. 28 | */ 29 | function grantAdmin(address account) external { 30 | grantRole(DEFAULT_ADMIN_ROLE, account); 31 | } 32 | 33 | /** 34 | * @notice Removes the account from the list of approved admins. 35 | * @dev Only callable by admins as enforced by `revokeRole`. 36 | * @param account The address to be removed from the approved list. 37 | */ 38 | function revokeAdmin(address account) external { 39 | revokeRole(DEFAULT_ADMIN_ROLE, account); 40 | } 41 | 42 | /** 43 | * @notice Returns one of the admins by index. 44 | * @param index The index of the admin to return from 0 to getAdminMemberCount() - 1. 45 | * @return account The address of the admin. 46 | */ 47 | function getAdminMember(uint256 index) external view returns (address account) { 48 | account = getRoleMember(DEFAULT_ADMIN_ROLE, index); 49 | } 50 | 51 | /** 52 | * @notice Checks how many accounts have been granted admin access. 53 | * @return count The number of accounts with admin access. 54 | */ 55 | function getAdminMemberCount() external view returns (uint256 count) { 56 | count = getRoleMemberCount(DEFAULT_ADMIN_ROLE); 57 | } 58 | 59 | /** 60 | * @notice Checks if the account provided is an admin. 61 | * @param account The address to check. 62 | * @return approved True if the account is an admin. 63 | * @dev This call is used by the royalty registry contract. 64 | */ 65 | function isAdmin(address account) external view returns (bool approved) { 66 | approved = hasRole(DEFAULT_ADMIN_ROLE, account); 67 | } 68 | 69 | /** 70 | * @notice This empty reserved space is put in place to allow future versions to add new 71 | * variables without shifting down storage in the inheritance chain. 72 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 73 | */ 74 | uint256[1000] private __gap; 75 | } 76 | -------------------------------------------------------------------------------- /libraries/AccountMigrationLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 6 | import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; 7 | import "@openzeppelin/contracts/utils/Strings.sol"; 8 | 9 | error AccountMigrationLibrary_Cannot_Migrate_Account_To_Itself(); 10 | error AccountMigrationLibrary_Signature_Verification_Failed(); 11 | 12 | /** 13 | * @title A library which confirms account migration signatures. 14 | * @notice Checks for a valid signature authorizing the migration of an account to a new address. 15 | * @dev This is shared by both the NFT contracts and MUSEENFTMarket, and the same signature authorizes both. 16 | */ 17 | library AccountMigrationLibrary { 18 | using ECDSA for bytes; 19 | using SignatureChecker for address; 20 | using Strings for uint256; 21 | 22 | /** 23 | * @notice Confirms the msg.sender is a Musee operator and that the signature provided is valid. 24 | * @param originalAddress The address of the account to be migrated. 25 | * @param newAddress The new address representing this account. 26 | * @param signature Message `I authorize Musee to migrate my account to ${newAccount.address.toLowerCase()}` 27 | * signed by the original account. 28 | */ 29 | function requireAuthorizedAccountMigration( 30 | address originalAddress, 31 | address newAddress, 32 | bytes calldata signature 33 | ) internal view { 34 | if (originalAddress == newAddress) { 35 | revert AccountMigrationLibrary_Cannot_Migrate_Account_To_Itself(); 36 | } 37 | bytes32 hash = abi 38 | .encodePacked("I authorize Musee to migrate my account to ", _toAsciiString(newAddress)) 39 | .toEthSignedMessageHash(); 40 | if (!originalAddress.isValidSignatureNow(hash, signature)) { 41 | revert AccountMigrationLibrary_Signature_Verification_Failed(); 42 | } 43 | } 44 | 45 | /** 46 | * @notice Converts an address into a string. 47 | * @dev From https://ethereum.stackexchange.com/questions/8346/convert-address-to-string 48 | */ 49 | function _toAsciiString(address x) private pure returns (string memory) { 50 | unchecked { 51 | bytes memory s = new bytes(42); 52 | s[0] = "0"; 53 | s[1] = "x"; 54 | for (uint256 i = 0; i < 20; ++i) { 55 | bytes1 b = bytes1(uint8(uint256(uint160(x)) / (2**(8 * (19 - i))))); 56 | bytes1 hi = bytes1(uint8(b) / 16); 57 | bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi)); 58 | s[2 * i + 2] = _char(hi); 59 | s[2 * i + 3] = _char(lo); 60 | } 61 | return string(s); 62 | } 63 | } 64 | 65 | /** 66 | * @notice Converts a byte to a UTF-8 character. 67 | */ 68 | function _char(bytes1 b) private pure returns (bytes1 c) { 69 | unchecked { 70 | if (uint8(b) < 10) return bytes1(uint8(b) + 0x30); 71 | else return bytes1(uint8(b) + 0x57); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mixins/NFT721Metadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | import "./NFT721Core.sol"; 8 | import "./NFT721Creator.sol"; 9 | 10 | /** 11 | * @title A mixin to extend the OpenZeppelin metadata implementation. 12 | */ 13 | abstract contract NFT721Metadata is NFT721Creator { 14 | using Strings for uint256; 15 | 16 | /// @notice Stores hashes minted by a creator to prevent duplicates. 17 | mapping(address => mapping(string => bool)) private creatorToIPFSHashToMinted; 18 | 19 | /** 20 | * @notice Emitted when the base URI used by NFTs created by this contract is updated. 21 | * @param baseURI The new base URI to use for all NFTs created by this contract. 22 | */ 23 | event BaseURIUpdated(string baseURI); 24 | 25 | /** 26 | * @notice Returns the IPFS path to the metadata JSON file for a given NFT. 27 | * @param tokenId The NFT to get the CID path for. 28 | * @return path The IPFS path to the metadata JSON file, without the base URI prefix. 29 | */ 30 | function getTokenIPFSPath(uint256 tokenId) external view returns (string memory path) { 31 | path = _tokenURIs[tokenId]; 32 | } 33 | 34 | /** 35 | * @notice Checks if the creator has already minted a given NFT. 36 | * @param creator The creator which may have minted this NFT already. 37 | * @param tokenIPFSPath The IPFS path to the metadata JSON file, without the base URI prefix. 38 | * @return hasMinted True if the creator has already minted this NFT. 39 | */ 40 | function getHasCreatorMintedIPFSHash(address creator, string calldata tokenIPFSPath) 41 | external 42 | view 43 | returns (bool hasMinted) 44 | { 45 | hasMinted = creatorToIPFSHashToMinted[creator][tokenIPFSPath]; 46 | } 47 | 48 | /** 49 | * @dev When a token is burned, remove record of it allowing that creator to re-mint the same NFT again in the future. 50 | */ 51 | function _burn(uint256 tokenId) internal virtual override { 52 | delete creatorToIPFSHashToMinted[msg.sender][_tokenURIs[tokenId]]; 53 | super._burn(tokenId); 54 | } 55 | 56 | /** 57 | * @dev The IPFS path should be the CID + file.extension, e.g. 58 | * `QmfPsfGwLhiJrU8t9HpG4wuyjgPo9bk8go4aQqSu9Qg4h7/metadata.json` 59 | */ 60 | function _setTokenIPFSPath(uint256 tokenId, string calldata _tokenIPFSPath) internal { 61 | // 46 is the minimum length for an IPFS content hash, it may be longer if paths are used 62 | require(bytes(_tokenIPFSPath).length >= 46, "NFT721Metadata: Invalid IPFS path"); 63 | require(!creatorToIPFSHashToMinted[msg.sender][_tokenIPFSPath], "NFT721Metadata: NFT was already minted"); 64 | 65 | creatorToIPFSHashToMinted[msg.sender][_tokenIPFSPath] = true; 66 | _setTokenURI(tokenId, _tokenIPFSPath); 67 | } 68 | 69 | function _updateBaseURI(string calldata _baseURI) internal { 70 | _setBaseURI(_baseURI); 71 | 72 | emit BaseURIUpdated(_baseURI); 73 | } 74 | 75 | /** 76 | * @notice This empty reserved space is put in place to allow future versions to add new 77 | * variables without shifting down storage in the inheritance chain. 78 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 79 | * @dev 1 slot was used with the addition of `creatorToIPFSHashToMinted`. 80 | */ 81 | uint256[999] private __gap; 82 | } 83 | -------------------------------------------------------------------------------- /libraries/LockedBalance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @title Library that handles locked balances efficiently using bit packing. 7 | */ 8 | library LockedBalance { 9 | /// @dev Tracks an account's total lockup per expiration time. 10 | struct Lockup { 11 | uint32 expiration; 12 | uint96 totalAmount; 13 | } 14 | 15 | struct Lockups { 16 | /// @dev Mapping from key to lockups. 17 | /// i) A key represents 2 lockups. The key for a lockup is `index / 2`. 18 | /// For instance, elements with index 25 and 24 would map to the same key. 19 | /// ii) The `value` for the `key` is split into two 128bits which are used to store the metadata for a lockup. 20 | mapping(uint256 => uint256) lockups; 21 | } 22 | 23 | // Masks used to split a uint256 into two equal pieces which represent two individual Lockups. 24 | uint256 private constant last128BitsMask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 25 | uint256 private constant first128BitsMask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000; 26 | 27 | // Masks used to retrieve or set the totalAmount value of a single Lockup. 28 | uint256 private constant firstAmountBitsMask = 0xFFFFFFFF000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 29 | uint256 private constant secondAmountBitsMask = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000; 30 | 31 | /** 32 | * @notice Clears the lockup at the index. 33 | */ 34 | function del(Lockups storage lockups, uint256 index) internal { 35 | unchecked { 36 | if (index % 2 == 0) { 37 | index /= 2; 38 | lockups.lockups[index] = (lockups.lockups[index] & last128BitsMask); 39 | } else { 40 | index /= 2; 41 | lockups.lockups[index] = (lockups.lockups[index] & first128BitsMask); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * @notice Sets the Lockup at the provided index. 48 | */ 49 | function set( 50 | Lockups storage lockups, 51 | uint256 index, 52 | uint256 expiration, 53 | uint256 totalAmount 54 | ) internal { 55 | unchecked { 56 | uint256 lockedBalanceBits = totalAmount | (expiration << 96); 57 | if (index % 2 == 0) { 58 | // set first 128 bits. 59 | index /= 2; 60 | lockups.lockups[index] = (lockups.lockups[index] & last128BitsMask) | (lockedBalanceBits << 128); 61 | } else { 62 | // set last 128 bits. 63 | index /= 2; 64 | lockups.lockups[index] = (lockups.lockups[index] & first128BitsMask) | lockedBalanceBits; 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * @notice Sets only the totalAmount for a lockup at the index. 71 | */ 72 | function setTotalAmount( 73 | Lockups storage lockups, 74 | uint256 index, 75 | uint256 totalAmount 76 | ) internal { 77 | unchecked { 78 | if (index % 2 == 0) { 79 | index /= 2; 80 | lockups.lockups[index] = (lockups.lockups[index] & firstAmountBitsMask) | (totalAmount << 128); 81 | } else { 82 | index /= 2; 83 | lockups.lockups[index] = (lockups.lockups[index] & secondAmountBitsMask) | totalAmount; 84 | } 85 | } 86 | } 87 | 88 | /** 89 | * @notice Returns the Lockup at the provided index. 90 | * @dev To get the lockup stored in the *first* 128 bits (first slot/lockup): 91 | * - we remove the last 128 bits (done by >> 128) 92 | * To get the lockup stored in the *last* 128 bits (second slot/lockup): 93 | * - we take the last 128 bits (done by % (2**128)) 94 | * Once the lockup is obtained: 95 | * - get `expiration` by peaking at the first 32 bits (done by >> 96) 96 | * - get `totalAmount` by peaking at the last 96 bits (done by % (2**96)) 97 | */ 98 | function get(Lockups storage lockups, uint256 index) internal view returns (Lockup memory balance) { 99 | unchecked { 100 | uint256 lockupMetadata = lockups.lockups[index / 2]; 101 | if (lockupMetadata == 0) { 102 | return balance; 103 | } 104 | uint128 lockedBalanceBits; 105 | if (index % 2 == 0) { 106 | // use first 128 bits. 107 | lockedBalanceBits = uint128(lockupMetadata >> 128); 108 | } else { 109 | // use last 128 bits. 110 | lockedBalanceBits = uint128(lockupMetadata % (2**128)); 111 | } 112 | // unpack the bits to retrieve the Lockup. 113 | balance.expiration = uint32(lockedBalanceBits >> 96); 114 | balance.totalAmount = uint96(lockedBalanceBits % (2**96)); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /mixins/OZ/ERC165Checker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.2.0/contracts/utils/introspection/ERC165.sol 7 | * Modified to allow checking multiple interfaces w/o checking general 165 support. 8 | */ 9 | 10 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 11 | 12 | /** 13 | * @title Library to query ERC165 support. 14 | * @dev Library used to query support of an interface declared via {IERC165}. 15 | * 16 | * Note that these functions return the actual result of the query: they do not 17 | * `revert` if an interface is not supported. It is up to the caller to decide 18 | * what to do in these cases. 19 | */ 20 | library ERC165Checker { 21 | // As per the EIP-165 spec, no interface should ever match 0xffffffff 22 | bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff; 23 | 24 | /** 25 | * @dev Returns true if `account` supports the {IERC165} interface, 26 | */ 27 | function supportsERC165(address account) internal view returns (bool) { 28 | // Any contract that implements ERC165 must explicitly indicate support of 29 | // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid 30 | return 31 | supportsERC165Interface(account, type(IERC165).interfaceId) && 32 | !supportsERC165Interface(account, _INTERFACE_ID_INVALID); 33 | } 34 | 35 | /** 36 | * @dev Returns true if `account` supports the interface defined by 37 | * `interfaceId`. Support for {IERC165} itself is queried automatically. 38 | * 39 | * See {IERC165-supportsInterface}. 40 | */ 41 | function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { 42 | // query support of both ERC165 as per the spec and support of _interfaceId 43 | return supportsERC165(account) && supportsERC165Interface(account, interfaceId); 44 | } 45 | 46 | /** 47 | * @dev Returns a boolean array where each value corresponds to the 48 | * interfaces passed in and whether they're supported or not. This allows 49 | * you to batch check interfaces for a contract where your expectation 50 | * is that some interfaces may not be supported. 51 | * 52 | * See {IERC165-supportsInterface}. 53 | * 54 | * _Available since v3.4._ 55 | */ 56 | function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool[] memory) { 57 | // an array of booleans corresponding to interfaceIds and whether they're supported or not 58 | bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); 59 | 60 | // query support of ERC165 itself 61 | if (supportsERC165(account)) { 62 | // query support of each interface in interfaceIds 63 | unchecked { 64 | for (uint256 i = 0; i < interfaceIds.length; ++i) { 65 | interfaceIdsSupported[i] = supportsERC165Interface(account, interfaceIds[i]); 66 | } 67 | } 68 | } 69 | 70 | return interfaceIdsSupported; 71 | } 72 | 73 | /** 74 | * @dev Returns true if `account` supports all the interfaces defined in 75 | * `interfaceIds`. Support for {IERC165} itself is queried automatically. 76 | * 77 | * Batch-querying can lead to gas savings by skipping repeated checks for 78 | * {IERC165} support. 79 | * 80 | * See {IERC165-supportsInterface}. 81 | */ 82 | function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { 83 | // query support of ERC165 itself 84 | if (!supportsERC165(account)) { 85 | return false; 86 | } 87 | 88 | // query support of each interface in _interfaceIds 89 | unchecked { 90 | for (uint256 i = 0; i < interfaceIds.length; ++i) { 91 | if (!supportsERC165Interface(account, interfaceIds[i])) { 92 | return false; 93 | } 94 | } 95 | } 96 | 97 | // all interfaces supported 98 | return true; 99 | } 100 | 101 | /** 102 | * @notice Query if a contract implements an interface, does not check ERC165 support 103 | * @param account The address of the contract to query for support of an interface 104 | * @param interfaceId The interface identifier, as specified in ERC-165 105 | * @return true if the contract at account indicates support of the interface with 106 | * identifier interfaceId, false otherwise 107 | * @dev Assumes that account contains a contract that supports ERC165, otherwise 108 | * the behavior of this method is undefined. This precondition can be checked 109 | * with {supportsERC165}. 110 | * Interface identification is specified in ERC-165. 111 | */ 112 | function supportsERC165Interface(address account, bytes4 interfaceId) internal view returns (bool) { 113 | bytes memory encodedParams = abi.encodeWithSelector(IERC165(account).supportsInterface.selector, interfaceId); 114 | (bool success, bytes memory result) = account.staticcall{ gas: 30000 }(encodedParams); 115 | if (result.length < 32) return false; 116 | return success && abi.decode(result, (bool)); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /mixins/NFT721Market.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | 7 | import "../interfaces/IGetRoyalties.sol"; 8 | import "../interfaces/IGetFees.sol"; 9 | import "../interfaces/IRoyaltyInfo.sol"; 10 | 11 | import "./MuseeTreasuryNode.sol"; 12 | import "./NFT721Creator.sol"; 13 | import "./Constants.sol"; 14 | 15 | /** 16 | * @title Holds a reference to the Musee Market and communicates fees to marketplaces. 17 | */ 18 | abstract contract NFT721Market is 19 | IGetRoyalties, 20 | IGetFees, 21 | IRoyaltyInfo, 22 | Constants, 23 | MuseeTreasuryNode, 24 | NFT721Creator 25 | { 26 | using AddressUpgradeable for address; 27 | 28 | /// @dev 10% of sales should go to the creator of the NFT. 29 | uint256 private constant ROYALTY_IN_BASIS_POINTS = 1000; 30 | /// @dev 10%, expressed as a denominator for more efficient calculations. 31 | uint256 private constant ROYALTY_RATIO = BASIS_POINTS / ROYALTY_IN_BASIS_POINTS; 32 | /// @notice The Musee market contract address. 33 | address private nftMarket; 34 | 35 | /** 36 | * @notice Emitted when the market contract address used for approvals is updated. 37 | * @param nftMarket The new market contract address. 38 | */ 39 | event NFTMarketUpdated(address indexed nftMarket); 40 | 41 | function _updateNFTMarket(address _nftMarket) internal { 42 | require(_nftMarket.isContract(), "NFT721Market: Market address is not a contract"); 43 | nftMarket = _nftMarket; 44 | 45 | emit NFTMarketUpdated(_nftMarket); 46 | } 47 | 48 | /** 49 | * @notice Returns an array of fees in basis points. 50 | * The expected recipients is communicated with `getFeeRecipients`. 51 | * @dev The tokenId param is ignored since all NFTs return the same value. 52 | * @return feesInBasisPoints The array of fees to be sent to each recipient, in basis points. 53 | */ 54 | function getFeeBps( 55 | uint256 /* id */ 56 | ) external pure override returns (uint256[] memory) { 57 | uint256[] memory result = new uint256[](1); 58 | result[0] = ROYALTY_IN_BASIS_POINTS; 59 | return result; 60 | } 61 | 62 | /** 63 | * @notice Returns an array of recipient addresses to which fees should be sent. 64 | * The expected fee amount is communicated with `getFeeBps`. 65 | * @param tokenId The tokenId of the NFT to get the royalty recipients for. 66 | * @return recipients An array of addresses to which royalties should be sent. 67 | */ 68 | function getFeeRecipients(uint256 tokenId) external view override returns (address payable[] memory) { 69 | require(_exists(tokenId), "ERC721Metadata: Query for nonexistent token"); 70 | 71 | address payable[] memory result = new address payable[](1); 72 | result[0] = getTokenCreatorPaymentAddress(tokenId); 73 | return result; 74 | } 75 | 76 | /** 77 | * @notice Get fee recipients and fees in a single call. 78 | * @dev The data is the same as when calling getFeeRecipients and getFeeBps separately. 79 | * @param tokenId The tokenId of the NFT to get the royalties for. 80 | * @return recipients An array of addresses to which royalties should be sent. 81 | * @return feesInBasisPoints The array of fees to be sent to each recipient address. 82 | */ 83 | function getRoyalties(uint256 tokenId) 84 | external 85 | view 86 | returns (address payable[] memory recipients, uint256[] memory feesInBasisPoints) 87 | { 88 | require(_exists(tokenId), "ERC721Metadata: Query for nonexistent token"); 89 | recipients = new address payable[](1); 90 | recipients[0] = getTokenCreatorPaymentAddress(tokenId); 91 | feesInBasisPoints = new uint256[](1); 92 | feesInBasisPoints[0] = ROYALTY_IN_BASIS_POINTS; 93 | } 94 | 95 | /** 96 | * @notice Returns the address of the Musee market contract. 97 | * @return market The Musee market contract address. 98 | */ 99 | function getNFTMarket() public view returns (address market) { 100 | market = address(nftMarket); 101 | } 102 | 103 | /** 104 | * @notice Returns the receiver and the amount to be sent for a secondary sale. 105 | * @param tokenId The tokenId of the NFT to get the royalty recipient and amount for. 106 | * @param salePrice The total price of the sale. 107 | * @return receiver The royalty recipient address for this sale. 108 | * @return royaltyAmount The total amount that should be sent to the `receiver`. 109 | */ 110 | function royaltyInfo(uint256 tokenId, uint256 salePrice) 111 | external 112 | view 113 | returns (address receiver, uint256 royaltyAmount) 114 | { 115 | receiver = getTokenCreatorPaymentAddress(tokenId); 116 | unchecked { 117 | royaltyAmount = salePrice / ROYALTY_RATIO; 118 | } 119 | } 120 | 121 | /** 122 | * @inheritdoc ERC165 123 | * @dev Checks the supported royalty interfaces. 124 | */ 125 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 126 | if ( 127 | interfaceId == type(IRoyaltyInfo).interfaceId || 128 | interfaceId == type(IGetRoyalties).interfaceId || 129 | interfaceId == type(IGetFees).interfaceId 130 | ) { 131 | return true; 132 | } 133 | return super.supportsInterface(interfaceId); 134 | } 135 | 136 | /** 137 | * @notice This empty reserved space is put in place to allow future versions to add new 138 | * variables without shifting down storage in the inheritance chain. 139 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 140 | */ 141 | uint256[1000] private __gap; 142 | } 143 | -------------------------------------------------------------------------------- /MUSEENFTMarket.sol: -------------------------------------------------------------------------------- 1 | /* 2 | MUSEE Protocol 3 | */ 4 | 5 | // SPDX-License-Identifier: MIT OR Apache-2.0 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 10 | import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 11 | 12 | import "./mixins/Constants.sol"; 13 | import "./mixins/MuseeTreasuryNode.sol"; 14 | import "./mixins/NFTMarketAuction.sol"; 15 | import "./mixins/NFTMarketBuyPrice.sol"; 16 | import "./mixins/NFTMarketCore.sol"; 17 | import "./mixins/NFTMarketCreators.sol"; 18 | import "./mixins/NFTMarketFees.sol"; 19 | import "./mixins/NFTMarketOffer.sol"; 20 | import "./mixins/NFTMarketPrivateSale.sol"; 21 | import "./mixins/NFTMarketReserveAuction.sol"; 22 | import "./mixins/SendValueWithFallbackWithdraw.sol"; 23 | 24 | /** 25 | * @title A market for NFTs on Musee. 26 | * @notice The Musee marketplace is a contract which allows traders to buy and sell NFTs. 27 | * It supports buying and selling via auctions, private sales, buy price, and offers. 28 | * @dev All sales in the Musee market will pay the creator 10% royalties on secondary sales. This is not specific 29 | * to NFTs minted on Musee, it should work for any NFT. If royalty information was not defined when the NFT was 30 | * originally deployed, it may be added using the [Royalty Registry](https://royaltyregistry.xyz/) which will be 31 | * respected by our market contract. 32 | */ 33 | contract MUSEENFTMarket is 34 | Constants, 35 | Initializable, 36 | MuseeTreasuryNode, 37 | NFTMarketCore, 38 | ReentrancyGuardUpgradeable, 39 | NFTMarketCreators, 40 | SendValueWithFallbackWithdraw, 41 | NFTMarketFees, 42 | NFTMarketAuction, 43 | NFTMarketReserveAuction, 44 | NFTMarketPrivateSale, 45 | NFTMarketBuyPrice, 46 | NFTMarketOffer 47 | { 48 | /** 49 | * @notice Set immutable variables for the implementation contract. 50 | * @dev Using immutable instead of constants allows us to use different values on testnet. 51 | * @param treasury The Musee Treasury contract address. 52 | * @param meth The METH ERC-20 token contract address. 53 | * @param royaltyRegistry The Royalty Registry contract address. 54 | * @param duration The duration of the auction in seconds. 55 | * @param marketProxyAddress The address of the proxy fronting this contract. 56 | */ 57 | constructor( 58 | address payable treasury, 59 | address meth, 60 | address royaltyRegistry, 61 | uint256 duration, 62 | address marketProxyAddress, 63 | address museeNftContract 64 | ) 65 | MuseeTreasuryNode(treasury) 66 | NFTMarketCore(meth) 67 | NFTMarketCreators(royaltyRegistry) 68 | NFTMarketReserveAuction(duration) 69 | NFTMarketPrivateSale(marketProxyAddress) // solhint-disable-next-line no-empty-blocks 70 | NFTMarketFees(museeNftContract) 71 | {} 72 | 73 | /** 74 | * @inheritdoc NFTMarketCore 75 | * @dev This is a no-op function required to avoid compile errors. 76 | */ 77 | function _transferFromEscrow( 78 | address nftContract, 79 | uint256 tokenId, 80 | address recipient, 81 | address authorizeSeller 82 | ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) { 83 | super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller); 84 | } 85 | /** 86 | * @notice Called once to configure the contract after the initial proxy deployment. 87 | * @dev This farms the initialize call out to inherited contracts as needed to initialize mutable variables. 88 | */ 89 | function initialize() external initializer { 90 | NFTMarketAuction._initializeNFTMarketAuction(); 91 | } 92 | 93 | /** 94 | * @inheritdoc NFTMarketCore 95 | * @dev This is a no-op function required to avoid compile errors. 96 | */ 97 | function _beforeAuctionStarted(address nftContract, uint256 tokenId) 98 | internal 99 | override(NFTMarketCore, NFTMarketBuyPrice, NFTMarketOffer) 100 | { 101 | super._beforeAuctionStarted(nftContract, tokenId); 102 | } 103 | 104 | /** 105 | * @inheritdoc NFTMarketCore 106 | * @dev This is a no-op function required to avoid compile errors. 107 | */ 108 | function _transferFromEscrow( 109 | address nftContract, 110 | uint256 tokenId, 111 | address recipient, 112 | address authorizeSeller 113 | ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) { 114 | super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller); 115 | } 116 | 117 | /** 118 | * @inheritdoc NFTMarketCore 119 | * @dev This is a no-op function required to avoid compile errors. 120 | */ 121 | function _transferFromEscrowIfAvailable( 122 | address nftContract, 123 | uint256 tokenId, 124 | address recipient 125 | ) internal override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) { 126 | super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient); 127 | } 128 | function initialize() external initializer { 129 | NFTMarketAuction._initializeNFTMarketAuction(); 130 | } 131 | /** 132 | * @inheritdoc NFTMarketCore 133 | * @dev This is a no-op function required to avoid compile errors. 134 | */ 135 | function _transferToEscrow(address nftContract, uint256 tokenId) 136 | internal 137 | override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) 138 | { 139 | super._transferToEscrow(nftContract, tokenId); 140 | } 141 | 142 | /** 143 | * @inheritdoc NFTMarketCore 144 | * @dev This is a no-op function required to avoid compile errors. 145 | */ 146 | function _getSellerFor(address nftContract, uint256 tokenId) 147 | internal 148 | view 149 | override(NFTMarketCore, NFTMarketReserveAuction, NFTMarketBuyPrice) 150 | returns (address payable seller) 151 | { 152 | seller = super._getSellerFor(nftContract, tokenId); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /mixins/NFTMarketCore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 7 | 8 | import "./Constants.sol"; 9 | 10 | import "../interfaces/IMethMarket.sol"; 11 | 12 | error NFTMarketCore_METH_Address_Is_Not_A_Contract(); 13 | error NFTMarketCore_Only_METH_Can_Transfer_ETH(); 14 | error NFTMarketCore_Seller_Not_Found(); 15 | 16 | /** 17 | * @title A place for common modifiers and functions used by various NFTMarket mixins, if any. 18 | * @dev This also leaves a gap which can be used to add a new mixin to the top of the inheritance tree. 19 | */ 20 | abstract contract NFTMarketCore is Constants { 21 | using AddressUpgradeable for address; 22 | 23 | /// @notice The METH ERC-20 token for managing escrow and lockup. 24 | IMethMarket internal immutable meth; 25 | 26 | constructor(address _meth) { 27 | // if (!_meth.isContract()) { 28 | // revert NFTMarketCore_METH_Address_Is_Not_A_Contract(); 29 | // } 30 | meth = IMethMarket(_meth); 31 | } 32 | 33 | /** 34 | * @notice Only used by METH. Any direct transfer from users will revert. 35 | */ 36 | receive() external payable { 37 | if (msg.sender != address(meth)) { 38 | revert NFTMarketCore_Only_METH_Can_Transfer_ETH(); 39 | } 40 | } 41 | 42 | /** 43 | * @notice If there is a buy price at this amount or lower, accept that and return true. 44 | */ 45 | function _autoAcceptBuyPrice( 46 | address nftContract, 47 | uint256 tokenId, 48 | uint256 amount 49 | ) internal virtual returns (bool); 50 | 51 | /** 52 | * @notice If there is a valid offer at the given price or higher, accept that and return true. 53 | */ 54 | function _autoAcceptOffer( 55 | address nftContract, 56 | uint256 tokenId, 57 | uint256 minAmount 58 | ) internal virtual returns (bool); 59 | 60 | /** 61 | * @notice Notify implementors when an auction has received its first bid. 62 | * Once a bid is received the sale is guaranteed to the auction winner 63 | * and other sale mechanisms become unavailable. 64 | * @dev Implementors of this interface should update internal state to reflect an auction has been kicked off. 65 | */ 66 | function _beforeAuctionStarted( 67 | address, /*nftContract*/ 68 | uint256 /*tokenId*/ // solhint-disable-next-line no-empty-blocks 69 | ) internal virtual { 70 | // No-op 71 | } 72 | 73 | /** 74 | * @notice Cancel the `msg.sender`'s offer if there is one, freeing up their METH balance. 75 | * @dev This should be used when it does not make sense to keep the original offer around, 76 | * e.g. if a collector accepts a Buy Price then keeping the offer around is not necessary. 77 | */ 78 | function _cancelSendersOffer(address nftContract, uint256 tokenId) internal virtual; 79 | 80 | /** 81 | * @notice Transfers the NFT from escrow and clears any state tracking this escrowed NFT. 82 | * @param authorizeSeller The address of the seller pending authorization. 83 | * Once it's been authorized by one of the escrow managers, it should be set to address(0) 84 | * indicated that it's no longer pending authorization. 85 | */ 86 | function _transferFromEscrow( 87 | address nftContract, 88 | uint256 tokenId, 89 | address recipient, 90 | address authorizeSeller 91 | ) internal virtual { 92 | if (authorizeSeller != address(0)) { 93 | revert NFTMarketCore_Seller_Not_Found(); 94 | } 95 | IERC721(nftContract).transferFrom(address(this), recipient, tokenId); 96 | } 97 | 98 | /** 99 | * @notice Transfers the NFT from escrow unless there is another reason for it to remain in escrow. 100 | */ 101 | function _transferFromEscrowIfAvailable( 102 | address nftContract, 103 | uint256 tokenId, 104 | address recipient 105 | ) internal virtual { 106 | IERC721(nftContract).transferFrom(address(this), recipient, tokenId); 107 | } 108 | 109 | /** 110 | * @notice Transfers an NFT into escrow, 111 | * if already there this requires the msg.sender is authorized to manage the sale of this NFT. 112 | */ 113 | function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual { 114 | IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId); 115 | } 116 | 117 | /** 118 | * @notice Gets the METH contract used to escrow offer funds. 119 | * @return methAddress The METH contract address. 120 | */ 121 | function getMethAddress() external view returns (address methAddress) { 122 | methAddress = address(meth); 123 | } 124 | 125 | /** 126 | * @dev Determines the minimum amount when increasing an existing offer or bid. 127 | */ 128 | function _getMinIncrement(uint256 currentAmount) internal pure returns (uint256) { 129 | uint256 minIncrement = currentAmount; 130 | unchecked { 131 | minIncrement /= MIN_PERCENT_INCREMENT_DENOMINATOR; 132 | } 133 | if (minIncrement == 0) { 134 | // Since minIncrement reduces from the currentAmount, this cannot overflow. 135 | // The next amount must be at least 1 wei greater than the current. 136 | return currentAmount + 1; 137 | } 138 | 139 | return minIncrement + currentAmount; 140 | } 141 | 142 | /** 143 | * @notice Checks who the seller for an NFT is, checking escrow or return the current owner if not in escrow. 144 | * @dev If the NFT did not have an escrowed seller to return, fall back to return the current owner. 145 | */ 146 | function _getSellerFor(address nftContract, uint256 tokenId) internal view virtual returns (address payable seller) { 147 | seller = payable(IERC721(nftContract).ownerOf(tokenId)); 148 | } 149 | 150 | /** 151 | * @notice Checks if an escrowed NFT is currently in active auction. 152 | * @return Returns false if the auction has ended, even if it has not yet been settled. 153 | */ 154 | function _isInActiveAuction(address nftContract, uint256 tokenId) internal view virtual returns (bool); 155 | 156 | /** 157 | * @notice This empty reserved space is put in place to allow future versions to add new 158 | * variables without shifting down storage in the inheritance chain. 159 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 160 | * @dev 50 slots were consumed by adding `ReentrancyGuard`. 161 | */ 162 | uint256[950] private __gap; 163 | } 164 | -------------------------------------------------------------------------------- /mixins/NFT721Mint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./OZ/ERC721Upgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 7 | 8 | import "./NFT721Creator.sol"; 9 | import "./NFT721Market.sol"; 10 | import "./NFT721Metadata.sol"; 11 | import "./NFT721ProxyCall.sol"; 12 | 13 | /** 14 | * @title Allows creators to mint NFTs. 15 | */ 16 | abstract contract NFT721Mint is 17 | Initializable, 18 | ERC721Upgradeable, 19 | NFT721ProxyCall, 20 | NFT721Creator, 21 | NFT721Market, 22 | NFT721Metadata 23 | { 24 | /// @notice A sequence ID to use for the next minted NFT. 25 | uint256 private nextTokenId; 26 | 27 | /** 28 | * @notice Emitted when a new NFT is minted. 29 | * @param creator The address of the creator & owner at this time this NFT was minted. 30 | * @param tokenId The tokenId of the newly minted NFT. 31 | * @param indexedTokenIPFSPath The CID of the newly minted NFT, indexed to enable watching 32 | * for mint events by the tokenCID. 33 | * @param tokenIPFSPath The actual CID of the newly minted NFT. 34 | */ 35 | event Minted( 36 | address indexed creator, 37 | uint256 indexed tokenId, 38 | string indexed indexedTokenIPFSPath, 39 | string tokenIPFSPath 40 | ); 41 | 42 | /** 43 | * @dev Called once after the initial deployment to set the initial tokenId. 44 | */ 45 | function _initializeNFT721Mint() internal onlyInitializing { 46 | // Use ID 1 for the first NFT tokenId 47 | nextTokenId = 1; 48 | } 49 | 50 | /** 51 | * @notice Allows a creator to mint an NFT. 52 | * @param tokenIPFSPath The IPFS path for the NFT to mint, without the leading base URI. 53 | * @return tokenId The tokenId of the newly minted NFT. 54 | */ 55 | function mint(string calldata tokenIPFSPath) public returns (uint256 tokenId) { 56 | unchecked { 57 | // Number of tokens cannot overflow 256 bits. 58 | tokenId = nextTokenId++; 59 | } 60 | _mint(msg.sender, tokenId); 61 | _updateTokenCreator(tokenId, payable(msg.sender)); 62 | _setTokenIPFSPath(tokenId, tokenIPFSPath); 63 | emit Minted(msg.sender, tokenId, tokenIPFSPath, tokenIPFSPath); 64 | } 65 | 66 | /** 67 | * @notice Allows a creator to mint an NFT and set approval for the Musee marketplace. 68 | * @dev This can be used by creators the first time they mint an NFT to save having to issue a separate 69 | * approval transaction before starting an auction. 70 | * @param tokenIPFSPath The IPFS path for the NFT to mint, without the leading base URI. 71 | * @return tokenId The tokenId of the newly minted NFT. 72 | */ 73 | function mintAndApproveMarket(string calldata tokenIPFSPath) external returns (uint256 tokenId) { 74 | tokenId = mint(tokenIPFSPath); 75 | setApprovalForAll(getNFTMarket(), true); 76 | } 77 | 78 | /** 79 | * @notice Allows a creator to mint an NFT and have creator revenue/royalties sent to an alternate address. 80 | * @param tokenIPFSPath The IPFS path for the NFT to mint, without the leading base URI. 81 | * @param tokenCreatorPaymentAddress The royalty recipient address to use for this NFT. 82 | * @return tokenId The tokenId of the newly minted NFT. 83 | */ 84 | function mintWithCreatorPaymentAddress(string calldata tokenIPFSPath, address payable tokenCreatorPaymentAddress) 85 | public 86 | returns (uint256 tokenId) 87 | { 88 | require(tokenCreatorPaymentAddress != address(0), "NFT721Mint: tokenCreatorPaymentAddress is required"); 89 | tokenId = mint(tokenIPFSPath); 90 | _setTokenCreatorPaymentAddress(tokenId, tokenCreatorPaymentAddress); 91 | } 92 | 93 | /** 94 | * @notice Allows a creator to mint an NFT and have creator revenue/royalties sent to an alternate address. 95 | * Also sets approval for the Musee marketplace. 96 | * @dev This can be used by creators the first time they mint an NFT to save having to issue a separate 97 | * approval transaction before starting an auction. 98 | * @param tokenIPFSPath The IPFS path for the NFT to mint, without the leading base URI. 99 | * @param tokenCreatorPaymentAddress The royalty recipient address to use for this NFT. 100 | * @return tokenId The tokenId of the newly minted NFT. 101 | */ 102 | function mintWithCreatorPaymentAddressAndApproveMarket( 103 | string calldata tokenIPFSPath, 104 | address payable tokenCreatorPaymentAddress 105 | ) external returns (uint256 tokenId) { 106 | tokenId = mintWithCreatorPaymentAddress(tokenIPFSPath, tokenCreatorPaymentAddress); 107 | setApprovalForAll(getNFTMarket(), true); 108 | } 109 | 110 | /** 111 | * @notice Allows a creator to mint an NFT and have creator revenue/royalties sent to an alternate address 112 | * which is defined by a contract call, typically a proxy contract address representing the payment terms. 113 | * @param tokenIPFSPath The IPFS path for the NFT to mint, without the leading base URI. 114 | * @param paymentAddressFactory The contract to call which will return the address to use for payments. 115 | * @param paymentAddressCallData The call details to sent to the factory provided. 116 | * @return tokenId The tokenId of the newly minted NFT. 117 | */ 118 | function mintWithCreatorPaymentFactory( 119 | string calldata tokenIPFSPath, 120 | address paymentAddressFactory, 121 | bytes calldata paymentAddressCallData 122 | ) public returns (uint256 tokenId) { 123 | address payable tokenCreatorPaymentAddress = _proxyCallAndReturnContractAddress( 124 | paymentAddressFactory, 125 | paymentAddressCallData 126 | ); 127 | tokenId = mintWithCreatorPaymentAddress(tokenIPFSPath, tokenCreatorPaymentAddress); 128 | } 129 | 130 | /** 131 | * @notice Allows a creator to mint an NFT and have creator revenue/royalties sent to an alternate address 132 | * which is defined by a contract call, typically a proxy contract address representing the payment terms. 133 | * Also sets approval for the Musee marketplace. 134 | * @dev This can be used by creators the first time they mint an NFT to save having to issue a separate 135 | * approval transaction before starting an auction. 136 | * @param tokenIPFSPath The IPFS path for the NFT to mint, without the leading base URI. 137 | * @param paymentAddressFactory The contract to call which will return the address to use for payments. 138 | * @param paymentAddressCallData The call details to sent to the factory provided. 139 | * @return tokenId The tokenId of the newly minted NFT. 140 | */ 141 | function mintWithCreatorPaymentFactoryAndApproveMarket( 142 | string calldata tokenIPFSPath, 143 | address paymentAddressFactory, 144 | bytes calldata paymentAddressCallData 145 | ) external returns (uint256 tokenId) { 146 | tokenId = mintWithCreatorPaymentFactory(tokenIPFSPath, paymentAddressFactory, paymentAddressCallData); 147 | setApprovalForAll(getNFTMarket(), true); 148 | } 149 | 150 | /** 151 | * @dev Explicit override to address compile errors. 152 | */ 153 | function _burn(uint256 tokenId) internal virtual override(ERC721Upgradeable, NFT721Creator, NFT721Metadata) { 154 | super._burn(tokenId); 155 | } 156 | 157 | /** 158 | * @notice Gets the tokenId of the next NFT minted. 159 | * @return tokenId The ID that the next NFT minted will use. 160 | */ 161 | function getNextTokenId() external view returns (uint256 tokenId) { 162 | tokenId = nextTokenId; 163 | } 164 | 165 | /** 166 | * @inheritdoc ERC165 167 | * @dev This is required to avoid compile errors. 168 | */ 169 | function supportsInterface(bytes4 interfaceId) 170 | public 171 | view 172 | virtual 173 | override(ERC721Upgradeable, NFT721Creator, NFT721Market) 174 | returns (bool) 175 | { 176 | return super.supportsInterface(interfaceId); 177 | } 178 | 179 | /** 180 | * @notice This empty reserved space is put in place to allow future versions to add new 181 | * variables without shifting down storage in the inheritance chain. 182 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 183 | */ 184 | uint256[1000] private __gap; 185 | } 186 | -------------------------------------------------------------------------------- /MUSEECollectionFactory.sol: -------------------------------------------------------------------------------- 1 | /* 2 | MUSEE Protocol 3 | */ 4 | 5 | // SPDX-License-Identifier: MIT OR Apache-2.0 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | import "./interfaces/ICollectionContractInitializer.sol"; 10 | import "./interfaces/ICollectionFactory.sol"; 11 | import "./interfaces/IProxyCall.sol"; 12 | import "./interfaces/IRoles.sol"; 13 | 14 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 15 | import "@openzeppelin/contracts/proxy/Clones.sol"; 16 | import "@openzeppelin/contracts/utils/Strings.sol"; 17 | 18 | /** 19 | * @title A factory to create NFT collections. 20 | * @notice Call this factory to create an NFT collection contract managed by a single creator. 21 | * @dev This creates and initializes an ERC-1165 minimal proxy pointing to the NFT collection contract template. 22 | */ 23 | contract MUSEECollectionFactory is ICollectionFactory { 24 | using AddressUpgradeable for address; 25 | using AddressUpgradeable for address payable; 26 | using Clones for address; 27 | using Strings for uint256; 28 | 29 | /** 30 | * @notice The contract address which manages common roles. 31 | * @dev Used by the collections for a shared operator definition. 32 | */ 33 | IRoles public rolesContract; 34 | 35 | /** 36 | * @notice The address of the template all new collections will leverage. 37 | */ 38 | address public implementation; 39 | 40 | /** 41 | * @notice The address of the proxy call contract implementation. 42 | * @dev Used by the collections to safely call another contract with arbitrary call data. 43 | */ 44 | IProxyCall public proxyCallContract; 45 | 46 | /** 47 | * @notice The implementation version new collections will use. 48 | * @dev This is auto-incremented each time the implementation is changed. 49 | */ 50 | uint256 public version; 51 | 52 | /** 53 | * @notice Emitted when a new collection is created from this factory. 54 | * @param collectionContract The address of the new NFT collection contract. 55 | * @param creator The address of the creator which owns the new collection. 56 | * @param version The implementation version used by the new collection. 57 | * @param name The name of the collection contract created. 58 | * @param symbol The symbol of the collection contract created. 59 | * @param nonce The nonce used by the creator when creating the collection, 60 | * used to define the address of the collection. 61 | */ 62 | event CollectionCreated( 63 | address indexed collectionContract, 64 | address indexed creator, 65 | uint256 indexed version, 66 | string name, 67 | string symbol, 68 | uint256 nonce 69 | ); 70 | 71 | /** 72 | * @notice Emitted when the implementation contract used by new collections is updated. 73 | * @param implementation The new implementation contract address. 74 | * @param version The version of the new implementation, auto-incremented. 75 | */ 76 | event ImplementationUpdated(address indexed implementation, uint256 indexed version); 77 | /** 78 | * @notice Emitted when the proxy call contract used by collections is updated. 79 | * @param proxyCallContract The new proxy call contract address. 80 | */ 81 | event ProxyCallContractUpdated(address indexed proxyCallContract); 82 | /** 83 | * @notice Emitted when the contract defining roles is updated. 84 | * @param rolesContract The new roles contract address. 85 | */ 86 | event RolesContractUpdated(address indexed rolesContract); 87 | 88 | event CollectionMade(address collectionAddress); 89 | 90 | modifier onlyAdmin() { 91 | require(rolesContract.isAdmin(msg.sender), "MUSEECollectionFactory: Caller does not have the Admin role"); 92 | _; 93 | } 94 | 95 | /** 96 | * @notice Defines requirements for the collection factory at deployment time. 97 | * @param _proxyCallContract The address of the proxy call contract implementation. 98 | * @param _rolesContract The address of the contract defining roles for collections to use. 99 | */ 100 | constructor(address _proxyCallContract, address _rolesContract) { 101 | _updateRolesContract(_rolesContract); 102 | _updateProxyCallContract(_proxyCallContract); 103 | } 104 | 105 | /** 106 | * @notice Allows Musee to change the collection implementation used for future collections. 107 | * This call will auto-increment the version. 108 | * Existing collections are not impacted. 109 | * @param _implementation The new collection implementation address. 110 | */ 111 | function adminUpdateImplementation(address _implementation) external onlyAdmin { 112 | _updateImplementation(_implementation); 113 | } 114 | 115 | /** 116 | * @notice Allows Musee to change the proxy call contract address. 117 | * @param _proxyCallContract The new proxy call contract address. 118 | */ 119 | function adminUpdateProxyCallContract(address _proxyCallContract) external onlyAdmin { 120 | _updateProxyCallContract(_proxyCallContract); 121 | } 122 | 123 | /** 124 | * @notice Allows Musee to change the admin role contract address. 125 | * @param _rolesContract The new admin role contract address. 126 | */ 127 | function adminUpdateRolesContract(address _rolesContract) external onlyAdmin { 128 | _updateRolesContract(_rolesContract); 129 | } 130 | 131 | /** 132 | * @notice Create a new collection contract. 133 | * @dev The nonce is required and must be unique for the msg.sender + implementation version, 134 | * otherwise this call will revert. 135 | * @param name The name for the new collection being created. 136 | * @param symbol The symbol for the new collection being created. 137 | * @param nonce An arbitrary value used to allow a creator to mint multiple collections. 138 | * @return collectionAddress The address of the new collection contract. 139 | */ 140 | 141 | function createCollection( 142 | string calldata name, 143 | string calldata symbol, 144 | uint256 nonce 145 | ) external returns (address collectionAddress) { 146 | require(bytes(symbol).length != 0, "MUSEECollectionFactory: Symbol is required"); 147 | 148 | // This reverts if the NFT was previously created using this implementation version + msg.sender + nonce 149 | collectionAddress = implementation.cloneDeterministic(_getSalt(msg.sender, nonce)); 150 | 151 | ICollectionContractInitializer(collectionAddress).initialize(payable(msg.sender), name, symbol); 152 | 153 | emit CollectionCreated(collectionAddress, msg.sender, version, name, symbol, nonce); 154 | } 155 | 156 | function _updateRolesContract(address _rolesContract) private { 157 | require(_rolesContract.isContract(), "MUSEECollectionFactory: RolesContract is not a contract"); 158 | rolesContract = IRoles(_rolesContract); 159 | 160 | emit RolesContractUpdated(_rolesContract); 161 | } 162 | 163 | /** 164 | * @notice Returns the address of a collection given the current implementation version, creator, and nonce. 165 | * This will return the same address whether the collection has already been created or not. 166 | * @param creator The creator of the collection. 167 | * @param nonce An arbitrary value used to allow a creator to mint multiple collections. 168 | * @return collectionAddress The address of the collection contract that would be created by this nonce. 169 | */ 170 | function predictCollectionAddress(address creator, uint256 nonce) external view returns (address collectionAddress) { 171 | collectionAddress = implementation.predictDeterministicAddress(_getSalt(creator, nonce)); 172 | } 173 | 174 | function _getSalt(address creator, uint256 nonce) private pure returns (bytes32) { 175 | return keccak256(abi.encodePacked(creator, nonce)); 176 | } 177 | 178 | /** 179 | * @dev Updates the implementation address, increments the version, and initializes the template. 180 | * Since the template is initialized when set, implementations cannot be re-used. 181 | * To downgrade the implementation, deploy the same bytecode again and then update to that. 182 | */ 183 | function _updateImplementation(address _implementation) private { 184 | require(_implementation.isContract(), "MUSEECollectionFactory: Implementation is not a contract"); 185 | implementation = _implementation; 186 | unchecked { 187 | // Version cannot overflow 256 bits. 188 | version++; 189 | } 190 | // The implementation is initialized when assigned so that others may not claim it as their own. 191 | ICollectionContractInitializer(_implementation).initialize( 192 | payable(address(rolesContract)), 193 | string(abi.encodePacked("Musee Collection Template v", version.toString())), 194 | string(abi.encodePacked("FCTv", version.toString())) 195 | ); 196 | 197 | emit ImplementationUpdated(_implementation, version); 198 | } 199 | 200 | function _updateProxyCallContract(address _proxyCallContract) private { 201 | require(_proxyCallContract.isContract(), "MUSEECollectionFactory: Proxy call address is not a contract"); 202 | proxyCallContract = IProxyCall(_proxyCallContract); 203 | 204 | emit ProxyCallContractUpdated(_proxyCallContract); 205 | } 206 | } 207 | 208 | /*╔═════════════════════════════════╗ 209 | ║ Finally Confirmed! ║ 210 | ╚═════════════════════════════════╝*/ 211 | -------------------------------------------------------------------------------- /mixins/OZ/AccessControlUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /** 4 | * Copied from https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts-upgradeable/v3.4.2-solc-0.7 5 | * Modified to support solc-8. 6 | * Using this instead of the new OZ implementation due to a change in storage slots used. 7 | * Also limited access of several functions as we will be using convenience wrappers. 8 | */ 9 | 10 | pragma solidity ^0.8.0; 11 | 12 | import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; 13 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 14 | import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; 15 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 16 | 17 | // solhint-disable 18 | 19 | /** 20 | * @title Implements role-based access control mechanisms. 21 | * @dev Contract module that allows children to implement role-based access 22 | * control mechanisms. 23 | * 24 | * Roles are referred to by their `bytes32` identifier. These should be exposed 25 | * in the external API and be unique. The best way to achieve this is by 26 | * using `public constant` hash digests: 27 | * 28 | * ``` 29 | * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); 30 | * ``` 31 | * 32 | * Roles can be used to represent a set of permissions. To restrict access to a 33 | * function call, use {hasRole}: 34 | * 35 | * ``` 36 | * function foo() public { 37 | * require(hasRole(MY_ROLE, msg.sender)); 38 | * ... 39 | * } 40 | * ``` 41 | * 42 | * Roles can be granted and revoked dynamically via the {grantRole} and 43 | * {revokeRole} functions. Each role has an associated admin role, and only 44 | * accounts that have a role's admin role can call {grantRole} and {revokeRole}. 45 | * 46 | * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means 47 | * that only accounts with this role will be able to grant or revoke other 48 | * roles. More complex role relationships can be created by using 49 | * {_setRoleAdmin}. 50 | * 51 | * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to 52 | * grant and revoke this role. Extra precautions should be taken to secure 53 | * accounts that have been granted it. 54 | */ 55 | abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable { 56 | function __AccessControl_init() internal onlyInitializing { 57 | __Context_init_unchained(); 58 | __AccessControl_init_unchained(); 59 | } 60 | 61 | function __AccessControl_init_unchained() internal onlyInitializing {} 62 | 63 | using EnumerableSet for EnumerableSet.AddressSet; 64 | using AddressUpgradeable for address; 65 | 66 | struct RoleData { 67 | EnumerableSet.AddressSet members; 68 | bytes32 adminRole; 69 | } 70 | 71 | mapping(bytes32 => RoleData) private _roles; 72 | 73 | bytes32 internal constant DEFAULT_ADMIN_ROLE = 0x00; 74 | 75 | /** 76 | * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` 77 | * 78 | * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite 79 | * {RoleAdminChanged} not being emitted signaling this. 80 | * 81 | * _Available since v3.1._ 82 | */ 83 | event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); 84 | 85 | /** 86 | * @dev Emitted when `account` is granted `role`. 87 | * 88 | * `sender` is the account that originated the contract call, an admin role 89 | * bearer except when using {_setupRole}. 90 | */ 91 | event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); 92 | 93 | /** 94 | * @dev Emitted when `account` is revoked `role`. 95 | * 96 | * `sender` is the account that originated the contract call: 97 | * - if using `revokeRole`, it is the admin role bearer 98 | * - if using `renounceRole`, it is the role bearer (i.e. `account`) 99 | */ 100 | event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); 101 | 102 | /** 103 | * @dev Returns `true` if `account` has been granted `role`. 104 | */ 105 | function hasRole(bytes32 role, address account) internal view returns (bool) { 106 | return _roles[role].members.contains(account); 107 | } 108 | 109 | /** 110 | * @dev Returns the number of accounts that have `role`. Can be used 111 | * together with {getRoleMember} to enumerate all bearers of a role. 112 | */ 113 | function getRoleMemberCount(bytes32 role) internal view returns (uint256) { 114 | return _roles[role].members.length(); 115 | } 116 | 117 | /** 118 | * @dev Returns one of the accounts that have `role`. `index` must be a 119 | * value between 0 and {getRoleMemberCount}, non-inclusive. 120 | * 121 | * Role bearers are not sorted in any particular way, and their ordering may 122 | * change at any point. 123 | * 124 | * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure 125 | * you perform all queries on the same block. See the following 126 | * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] 127 | * for more information. 128 | */ 129 | function getRoleMember(bytes32 role, uint256 index) internal view returns (address) { 130 | return _roles[role].members.at(index); 131 | } 132 | 133 | /** 134 | * @dev Returns the admin role that controls `role`. See {grantRole} and 135 | * {revokeRole}. 136 | * 137 | * To change a role's admin, use {_setRoleAdmin}. 138 | */ 139 | function getRoleAdmin(bytes32 role) internal view returns (bytes32) { 140 | return _roles[role].adminRole; 141 | } 142 | 143 | /** 144 | * @dev Grants `role` to `account`. 145 | * 146 | * If `account` had not been already granted `role`, emits a {RoleGranted} 147 | * event. 148 | * 149 | * Requirements: 150 | * 151 | * - the caller must have ``role``'s admin role. 152 | */ 153 | function grantRole(bytes32 role, address account) internal virtual { 154 | require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to grant"); 155 | 156 | _grantRole(role, account); 157 | } 158 | 159 | /** 160 | * @dev Revokes `role` from `account`. 161 | * 162 | * If `account` had been granted `role`, emits a {RoleRevoked} event. 163 | * 164 | * Requirements: 165 | * 166 | * - the caller must have ``role``'s admin role. 167 | */ 168 | function revokeRole(bytes32 role, address account) internal virtual { 169 | require(hasRole(_roles[role].adminRole, _msgSender()), "AccessControl: sender must be an admin to revoke"); 170 | 171 | _revokeRole(role, account); 172 | } 173 | 174 | /** 175 | * @dev Revokes `role` from the calling account. 176 | * 177 | * Roles are often managed via {grantRole} and {revokeRole}: this function's 178 | * purpose is to provide a mechanism for accounts to lose their privileges 179 | * if they are compromised (such as when a trusted device is misplaced). 180 | * 181 | * If the calling account had been granted `role`, emits a {RoleRevoked} 182 | * event. 183 | * 184 | * Requirements: 185 | * 186 | * - the caller must be `account`. 187 | */ 188 | function renounceRole(bytes32 role, address account) internal virtual { 189 | require(account == _msgSender(), "AccessControl: can only renounce roles for self"); 190 | 191 | _revokeRole(role, account); 192 | } 193 | 194 | /** 195 | * @dev Grants `role` to `account`. 196 | * 197 | * If `account` had not been already granted `role`, emits a {RoleGranted} 198 | * event. Note that unlike {grantRole}, this function doesn't perform any 199 | * checks on the calling account. 200 | * 201 | * [WARNING] 202 | * ==== 203 | * This function should only be called from the constructor when setting 204 | * up the initial roles for the system. 205 | * 206 | * Using this function in any other way is effectively circumventing the admin 207 | * system imposed by {AccessControl}. 208 | * ==== 209 | */ 210 | function _setupRole(bytes32 role, address account) internal { 211 | _grantRole(role, account); 212 | } 213 | 214 | /** 215 | * @dev Sets `adminRole` as ``role``'s admin role. 216 | * 217 | * Emits a {RoleAdminChanged} event. 218 | */ 219 | function _setRoleAdmin(bytes32 role, bytes32 adminRole) private { 220 | emit RoleAdminChanged(role, _roles[role].adminRole, adminRole); 221 | _roles[role].adminRole = adminRole; 222 | } 223 | 224 | function _grantRole(bytes32 role, address account) private { 225 | if (_roles[role].members.add(account)) { 226 | emit RoleGranted(role, account, _msgSender()); 227 | } 228 | } 229 | 230 | function _revokeRole(bytes32 role, address account) private { 231 | if (_roles[role].members.remove(account)) { 232 | emit RoleRevoked(role, account, _msgSender()); 233 | } 234 | } 235 | 236 | /** 237 | * @notice This empty reserved space is put in place to allow future versions to add new 238 | * variables without shifting down storage in the inheritance chain. 239 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 240 | */ 241 | uint256[49] private __gap; 242 | } 243 | -------------------------------------------------------------------------------- /mixins/NFTMarketFees.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 7 | 8 | import "./Constants.sol"; 9 | import "./MuseeTreasuryNode.sol"; 10 | import "./SendValueWithFallbackWithdraw.sol"; 11 | 12 | /** 13 | * @title A mixin to distribute funds when an NFT is sold. 14 | */ 15 | abstract contract NFTMarketFees is Constants, Initializable, MuseeTreasuryNode, SendValueWithFallbackWithdraw { 16 | /** 17 | * @dev Removing old unused variables in an upgrade safe way. Was: 18 | * uint256 private _primaryMuseeFeeBasisPoints; 19 | * uint256 private _secondaryMuseeFeeBasisPoints; 20 | * uint256 private _secondaryCreatorFeeBasisPoints; 21 | * mapping(address => mapping(uint256 => bool)) private _nftContractToTokenIdToFirstSaleCompleted; 22 | */ 23 | uint256[4] private __gap_was_fees; 24 | 25 | /// @notice The royalties sent to creator recipients on secondary sales. 26 | uint256 private constant CREATOR_ROYALTY_DENOMINATOR = BASIS_POINTS / 1000; // 10% 27 | /// @notice The fee collected by Musee for sales facilitated by this market contract. 28 | uint256 private constant MUSEE_FEE_DENOMINATOR = BASIS_POINTS / 500; // 5% 29 | /// @notice Musee NFT collection address 30 | address private immutable MUSEE_NFT_CONTRACT; 31 | 32 | /** 33 | * @notice Configures the musee collection address 34 | * @param museeNftContract The Musee Collection Address 35 | */ 36 | constructor(address museeNftContract) { 37 | MUSEE_NFT_CONTRACT = museeNftContract; 38 | } 39 | 40 | /** 41 | * @notice Distributes funds to musee, creator recipients, and NFT owner after a sale. 42 | */ 43 | // solhint-disable-next-line code-complexity 44 | function _distributeFunds( 45 | address nftContract, 46 | uint256 tokenId, 47 | address payable seller, 48 | uint256 price 49 | ) 50 | internal 51 | returns ( 52 | uint256 museeFee, 53 | uint256 creatorFee, 54 | uint256 ownerRev 55 | ) 56 | { 57 | address payable[] memory creatorRecipients; 58 | uint256[] memory creatorShares; 59 | 60 | address payable ownerRevTo; 61 | (museeFee, creatorRecipients, creatorShares, creatorFee, ownerRevTo, ownerRev) = _getFees( 62 | nftContract, 63 | tokenId, 64 | seller, 65 | price 66 | ); 67 | 68 | _sendValueWithFallbackWithdraw(getMuseeTreasury(), museeFee, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT); 69 | 70 | if (creatorFee != 0) { 71 | if (creatorRecipients.length > 1) { 72 | uint256 maxCreatorIndex = creatorRecipients.length; 73 | unchecked { 74 | // maxCreatorIndex cannot underflow due to the if above 75 | --maxCreatorIndex; 76 | } 77 | 78 | if (maxCreatorIndex > MAX_ROYALTY_RECIPIENTS_INDEX) { 79 | maxCreatorIndex = MAX_ROYALTY_RECIPIENTS_INDEX; 80 | } 81 | 82 | // Determine the total shares defined so it can be leveraged to distribute below 83 | uint256 totalShares; 84 | unchecked { 85 | // The array length cannot overflow 256 bits. 86 | for (uint256 i = 0; i <= maxCreatorIndex; ++i) { 87 | if (creatorShares[i] > BASIS_POINTS) { 88 | // If the numbers are >100% we ignore the fee recipients and pay just the first instead 89 | maxCreatorIndex = 0; 90 | break; 91 | } 92 | // The check above ensures totalShares wont overflow. 93 | totalShares += creatorShares[i]; 94 | } 95 | } 96 | if (totalShares == 0) { 97 | maxCreatorIndex = 0; 98 | } 99 | 100 | // Send payouts to each additional recipient if more than 1 was defined 101 | uint256 totalRoyaltiesDistributed; 102 | for (uint256 i = 1; i <= maxCreatorIndex; ) { 103 | uint256 royalty = (creatorFee * creatorShares[i]) / totalShares; 104 | totalRoyaltiesDistributed += royalty; 105 | _sendValueWithFallbackWithdraw(creatorRecipients[i], royalty, SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS); 106 | unchecked { 107 | ++i; 108 | } 109 | } 110 | 111 | // Send the remainder to the 1st creator, rounding in their favor 112 | _sendValueWithFallbackWithdraw( 113 | creatorRecipients[0], 114 | creatorFee - totalRoyaltiesDistributed, 115 | SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS 116 | ); 117 | } else { 118 | _sendValueWithFallbackWithdraw(creatorRecipients[0], creatorFee, SEND_VALUE_GAS_LIMIT_MULTIPLE_RECIPIENTS); 119 | } 120 | } 121 | _sendValueWithFallbackWithdraw(ownerRevTo, ownerRev, SEND_VALUE_GAS_LIMIT_SINGLE_RECIPIENT); 122 | } 123 | 124 | /** 125 | * @notice Returns how funds will be distributed for a sale at the given price point. 126 | * @param nftContract The address of the NFT contract. 127 | * @param tokenId The id of the NFT. 128 | * @param price The sale price to calculate the fees for. 129 | * @return museeFee How much will be sent to the Musee treasury. 130 | * @return creatorRev How much will be sent across all the `creatorRecipients` defined. 131 | * @return creatorRecipients The addresses of the recipients to receive a portion of the creator fee. 132 | * @return creatorShares The percentage of the creator fee to be distributed to each `creatorRecipient`. 133 | * If there is only one `creatorRecipient`, this may be an empty array. 134 | * Otherwise `creatorShares.length` == `creatorRecipients.length`. 135 | * @return ownerRev How much will be sent to the owner/seller of the NFT. 136 | * If the NFT is being sold by the creator, this may be 0 and the full revenue will appear as `creatorRev`. 137 | * @return owner The address of the owner of the NFT. 138 | * If `ownerRev` is 0, this may be `address(0)`. 139 | */ 140 | function getFeesAndRecipients( 141 | address nftContract, 142 | uint256 tokenId, 143 | uint256 price 144 | ) 145 | external 146 | view 147 | returns ( 148 | uint256 museeFee, 149 | uint256 creatorRev, 150 | address payable[] memory creatorRecipients, 151 | uint256[] memory creatorShares, 152 | uint256 ownerRev, 153 | address payable owner 154 | ) 155 | { 156 | address payable seller = _getSellerFor(nftContract, tokenId); 157 | (museeFee, creatorRecipients, creatorShares, creatorRev, owner, ownerRev) = _getFees( 158 | nftContract, 159 | tokenId, 160 | seller, 161 | price 162 | ); 163 | } 164 | 165 | /** 166 | * @notice Calculates how funds should be distributed for the given sale details. 167 | * @dev When the NFT is being sold by the `tokenCreator`, all the seller revenue will 168 | * be split with the royalty recipients defined for that NFT. 169 | */ 170 | function _getFees( 171 | address nftContract, 172 | uint256 tokenId, 173 | address payable seller, 174 | uint256 price 175 | ) 176 | private 177 | view 178 | returns ( 179 | uint256 museeFee, 180 | address payable[] memory creatorRecipients, 181 | uint256[] memory creatorShares, 182 | uint256 creatorRev, 183 | address payable ownerRevTo, 184 | uint256 ownerRev 185 | ) 186 | { 187 | bool isCreator = false; 188 | // lookup for tokenCreator 189 | try ITokenCreator(nftContract).tokenCreator{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns ( 190 | address payable _creator 191 | ) { 192 | isCreator = _creator == seller; 193 | } catch // solhint-disable-next-line no-empty-blocks 194 | { 195 | // Fall through 196 | } 197 | 198 | (creatorRecipients, creatorShares) = _getCreatorPaymentInfo(nftContract, tokenId); 199 | 200 | // Calculate the Musee fee 201 | unchecked { 202 | // SafeMath is not required when dividing by a non-zero constant. 203 | uint256 nftBalance = IERC721(MUSEE_NFT_CONTRACT).balanceOf(seller); 204 | if (nftBalance != 0) { 205 | museeFee = 0; 206 | } else { 207 | museeFee = price / MUSEE_FEE_DENOMINATOR; 208 | } 209 | } 210 | 211 | if (creatorRecipients.length != 0) { 212 | if (isCreator || (creatorRecipients.length == 1 && seller == creatorRecipients[0])) { 213 | // When sold by the creator, all revenue is split if applicable. 214 | unchecked { 215 | // museeFee is always < price. 216 | creatorRev = price - museeFee; 217 | } 218 | } else { 219 | // Rounding favors the owner first, then creator, and musee last. 220 | unchecked { 221 | // SafeMath is not required when dividing by a non-zero constant. 222 | creatorRev = price / CREATOR_ROYALTY_DENOMINATOR; 223 | } 224 | ownerRevTo = seller; 225 | ownerRev = price - museeFee - creatorRev; 226 | } 227 | } else { 228 | // No royalty recipients found. 229 | ownerRevTo = seller; 230 | unchecked { 231 | // museeFee is always < price. 232 | ownerRev = price - museeFee; 233 | } 234 | } 235 | } 236 | 237 | /** 238 | * @notice This empty reserved space is put in place to allow future versions to add new 239 | * variables without shifting down storage in the inheritance chain. 240 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 241 | */ 242 | uint256[1000] private __gap; 243 | } 244 | -------------------------------------------------------------------------------- /mixins/NFTMarketCreators.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./OZ/ERC165Checker.sol"; 6 | import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 7 | 8 | import "./Constants.sol"; 9 | 10 | import "../interfaces/IGetFees.sol"; 11 | import "../interfaces/IGetRoyalties.sol"; 12 | import "../interfaces/IOwnable.sol"; 13 | import "../interfaces/IRoyaltyInfo.sol"; 14 | import "../interfaces/ITokenCreator.sol"; 15 | import "@manifoldxyz/royalty-registry-solidity/contracts/IRoyaltyRegistry.sol"; 16 | 17 | error NFTMarketCreators_Address_Does_Not_Support_IRoyaltyRegistry(); 18 | 19 | /** 20 | * @title A mixin for associating creators to NFTs. 21 | * @dev In the future this may store creators directly in order to support NFTs created on a different platform. 22 | */ 23 | abstract contract NFTMarketCreators is 24 | Constants, 25 | ReentrancyGuardUpgradeable // Adding this unused mixin to help with linearization 26 | { 27 | using ERC165Checker for address; 28 | 29 | IRoyaltyRegistry private immutable royaltyRegistry; 30 | 31 | /** 32 | * @notice Configures the registry allowing for royalty overrides to be defined. 33 | * @param _royaltyRegistry The registry to use for royalty overrides. 34 | */ 35 | constructor(address _royaltyRegistry) { 36 | if (!_royaltyRegistry.supportsInterface(type(IRoyaltyRegistry).interfaceId)) { 37 | revert NFTMarketCreators_Address_Does_Not_Support_IRoyaltyRegistry(); 38 | } 39 | royaltyRegistry = IRoyaltyRegistry(_royaltyRegistry); 40 | } 41 | 42 | /** 43 | * @notice Looks up the royalty payment configuration for a given NFT. 44 | * If more than 5 royalty recipients are defined, only the first 5 are sent royalties - the others are ignored. 45 | * The percents distributed will always be normalized to exactly 10%, when multiple recipients are defined 46 | * we use the values returned to determine how much of that 10% each recipient should get. 47 | * @dev This will check various royalty APIs on the NFT and the royalty override 48 | * if one was registered with the royalty registry. This aims to send royalties 49 | * in the manner requested by the NFT owner, regardless of where the NFT was minted. 50 | */ 51 | // solhint-disable-next-line code-complexity 52 | function _getCreatorPaymentInfo(address nftContract, uint256 tokenId) 53 | internal 54 | view 55 | returns (address payable[] memory recipients, uint256[] memory splitPerRecipientInBasisPoints) 56 | { 57 | // All NFTs implement 165 so we skip that check, individual interfaces should return false if 165 is not implemented 58 | 59 | // 1st priority: ERC-2981 60 | if (nftContract.supportsERC165Interface(type(IRoyaltyInfo).interfaceId)) { 61 | try IRoyaltyInfo(nftContract).royaltyInfo{ gas: READ_ONLY_GAS_LIMIT }(tokenId, BASIS_POINTS) returns ( 62 | address receiver, 63 | uint256 royaltyAmount 64 | ) { 65 | // Manifold contracts return (address(this), 0) when royalties are not defined 66 | // - so ignore results when the amount is 0 67 | if (royaltyAmount > 0) { 68 | recipients = new address payable[](1); 69 | recipients[0] = payable(receiver); 70 | // splitPerRecipientInBasisPoints is not relevant when only 1 recipient is defined 71 | return (recipients, splitPerRecipientInBasisPoints); 72 | } 73 | } catch // solhint-disable-next-line no-empty-blocks 74 | { 75 | // Fall through 76 | } 77 | } 78 | 79 | // 2nd priority: getRoyalties 80 | if (nftContract.supportsERC165Interface(type(IGetRoyalties).interfaceId)) { 81 | try IGetRoyalties(nftContract).getRoyalties{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns ( 82 | address payable[] memory _recipients, 83 | uint256[] memory recipientBasisPoints 84 | ) { 85 | uint256 recipientLen = _recipients.length; 86 | if (recipientLen != 0 && recipientLen == recipientBasisPoints.length) { 87 | return (_recipients, recipientBasisPoints); 88 | } 89 | } catch // solhint-disable-next-line no-empty-blocks 90 | { 91 | // Fall through 92 | } 93 | } 94 | 95 | /* Overrides must support ERC-165 when registered, except for overrides defined by the registry owner. 96 | If that results in an override w/o 165 we may need to upgrade the market to support or ignore that override. */ 97 | // The registry requires overrides are not 0 and contracts when set. 98 | // If no override is set, the nftContract address is returned. 99 | 100 | try royaltyRegistry.getRoyaltyLookupAddress{ gas: READ_ONLY_GAS_LIMIT }(nftContract) returns ( 101 | address overrideContract 102 | ) { 103 | if (overrideContract != nftContract) { 104 | nftContract = overrideContract; 105 | 106 | // The functions above are repeated here if an override is set. 107 | 108 | // 3rd priority: ERC-2981 override 109 | if (nftContract.supportsERC165Interface(type(IRoyaltyInfo).interfaceId)) { 110 | try IRoyaltyInfo(nftContract).royaltyInfo{ gas: READ_ONLY_GAS_LIMIT }(tokenId, BASIS_POINTS) returns ( 111 | address receiver, 112 | uint256 /* royaltyAmount */ 113 | ) { 114 | recipients = new address payable[](1); 115 | recipients[0] = payable(receiver); 116 | // splitPerRecipientInBasisPoints is not relevant when only 1 recipient is defined 117 | return (recipients, splitPerRecipientInBasisPoints); 118 | } catch // solhint-disable-next-line no-empty-blocks 119 | { 120 | // Fall through 121 | } 122 | } 123 | 124 | // 4th priority: getRoyalties override 125 | if (recipients.length == 0 && nftContract.supportsERC165Interface(type(IGetRoyalties).interfaceId)) { 126 | try IGetRoyalties(nftContract).getRoyalties{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns ( 127 | address payable[] memory _recipients, 128 | uint256[] memory recipientBasisPoints 129 | ) { 130 | uint256 recipientLen = _recipients.length; 131 | if (recipientLen != 0 && recipientLen == recipientBasisPoints.length) { 132 | return (_recipients, recipientBasisPoints); 133 | } 134 | } catch // solhint-disable-next-line no-empty-blocks 135 | { 136 | // Fall through 137 | } 138 | } 139 | } 140 | } catch // solhint-disable-next-line no-empty-blocks 141 | { 142 | // Ignore out of gas errors and continue using the nftContract address 143 | } 144 | 145 | // 5th priority: getFee* from contract or override 146 | if (nftContract.supportsERC165Interface(type(IGetFees).interfaceId)) { 147 | try IGetFees(nftContract).getFeeRecipients{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns ( 148 | address payable[] memory _recipients 149 | ) { 150 | uint256 recipientLen = _recipients.length; 151 | if (recipientLen != 0) { 152 | try IGetFees(nftContract).getFeeBps{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns ( 153 | uint256[] memory recipientBasisPoints 154 | ) { 155 | if (recipientLen == recipientBasisPoints.length) { 156 | return (_recipients, recipientBasisPoints); 157 | } 158 | } catch // solhint-disable-next-line no-empty-blocks 159 | { 160 | // Fall through 161 | } 162 | } 163 | } catch // solhint-disable-next-line no-empty-blocks 164 | { 165 | // Fall through 166 | } 167 | } 168 | 169 | // 6th priority: tokenCreator w/ or w/o requiring 165 from contract or override 170 | try ITokenCreator(nftContract).tokenCreator{ gas: READ_ONLY_GAS_LIMIT }(tokenId) returns ( 171 | address payable _creator 172 | ) { 173 | // Only pay the tokenCreator if there wasn't another royalty defined 174 | recipients = new address payable[](1); 175 | recipients[0] = _creator; 176 | // splitPerRecipientInBasisPoints is not relevant when only 1 recipient is defined 177 | return (recipients, splitPerRecipientInBasisPoints); 178 | } catch // solhint-disable-next-line no-empty-blocks 179 | { 180 | // Fall through 181 | } 182 | 183 | // 7th priority: owner from contract or override 184 | try IOwnable(nftContract).owner{ gas: READ_ONLY_GAS_LIMIT }() returns (address owner) { 185 | // Only pay the owner if there wasn't another royalty defined 186 | recipients = new address payable[](1); 187 | recipients[0] = payable(owner); 188 | // splitPerRecipientInBasisPoints is not relevant when only 1 recipient is defined 189 | return (recipients, splitPerRecipientInBasisPoints); 190 | } catch // solhint-disable-next-line no-empty-blocks 191 | { 192 | // Fall through 193 | } 194 | 195 | // If no valid payment address or creator is found, return 0 recipients 196 | } 197 | 198 | /** 199 | * @notice Returns the address of the registry allowing for royalty configuration overrides. 200 | * @return registry The address of the royalty registry contract. 201 | */ 202 | function getRoyaltyRegistry() public view returns (address registry) { 203 | return address(royaltyRegistry); 204 | } 205 | 206 | /** 207 | * @notice This empty reserved space is put in place to allow future versions to add new 208 | * variables without shifting down storage in the inheritance chain. 209 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 210 | * @dev 500 slots were consumed with the addition of `SendValueWithFallbackWithdraw`. 211 | */ 212 | uint256[500] private __gap; 213 | } 214 | -------------------------------------------------------------------------------- /mixins/NFTMarketPrivateSale.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 7 | 8 | import "./NFTMarketFees.sol"; 9 | 10 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 11 | 12 | error NFTMarketPrivateSale_Can_Be_Offered_For_24Hrs_Max(); 13 | error NFTMarketPrivateSale_Signature_Canceled_Or_Already_Claimed(); 14 | error NFTMarketPrivateSale_Proxy_Address_Is_Not_A_Contract(); 15 | error NFTMarketPrivateSale_Sale_Expired(); 16 | error NFTMarketPrivateSale_Signature_Verification_Failed(); 17 | error NFTMarketPrivateSale_Too_Much_Value_Provided(); 18 | 19 | /** 20 | * @title Allows owners to offer an NFT for sale to a specific collector. 21 | * @notice Private sales are authorized by the seller with an EIP-712 signature. 22 | * @dev Private sale offers must be accepted by the buyer before they expire, typically in 24 hours. 23 | */ 24 | abstract contract NFTMarketPrivateSale is NFTMarketFees { 25 | using AddressUpgradeable for address; 26 | using ECDSA for bytes32; 27 | 28 | /// @dev This value was replaced with an immutable version. 29 | bytes32 private __gap_was_DOMAIN_SEPARATOR; 30 | 31 | /// @notice Tracks if a private sale has already been used. 32 | /// @dev Maps nftContract -> tokenId -> buyer -> seller -> amount -> deadline -> invalidated. 33 | // solhint-disable-next-line max-line-length 34 | mapping(address => mapping(uint256 => mapping(address => mapping(address => mapping(uint256 => mapping(uint256 => bool)))))) 35 | private privateSaleInvalidated; 36 | 37 | /// @notice The domain used in EIP-712 signatures. 38 | /// @dev It is not a constant so that the chainId can be determined dynamically. 39 | /// If multiple classes use EIP-712 signatures in the future this can move to a shared file. 40 | bytes32 private immutable DOMAIN_SEPARATOR; 41 | 42 | /// @notice The hash of the private sale method signature used for EIP-712 signatures. 43 | bytes32 private constant BUY_FROM_PRIVATE_SALE_TYPEHASH = 44 | keccak256("BuyFromPrivateSale(address nftContract,uint256 tokenId,address buyer,uint256 price,uint256 deadline)"); 45 | /// @notice The name used in the EIP-712 domain. 46 | /// @dev If multiple classes use EIP-712 signatures in the future this can move to the shared constants file. 47 | string private constant NAME = "MUSEENFTMarket"; 48 | 49 | /** 50 | * @notice Emitted when an NFT is sold in a private sale. 51 | * @dev The total amount of this sale is `museeFee` + `creatorFee` + `ownerRev`. 52 | * @param nftContract The address of the NFT contract. 53 | * @param tokenId The ID of the NFT. 54 | * @param seller The address of the seller. 55 | * @param buyer The address of the buyer. 56 | * @param museeFee The amount of ETH that was sent to Musee for this sale. 57 | * @param creatorFee The amount of ETH that was sent to the creator for this sale. 58 | * @param ownerRev The amount of ETH that was sent to the owner for this sale. 59 | * @param deadline When the private sale offer was set to expire. 60 | */ 61 | event PrivateSaleFinalized( 62 | address indexed nftContract, 63 | uint256 indexed tokenId, 64 | address indexed seller, 65 | address buyer, 66 | uint256 museeFee, 67 | uint256 creatorFee, 68 | uint256 ownerRev, 69 | uint256 deadline 70 | ); 71 | 72 | /** 73 | * @notice Configures the contract to accept EIP-712 signatures. 74 | * @param marketProxyAddress The address of the proxy contract which will be called when accepting a private sale. 75 | */ 76 | constructor(address marketProxyAddress) { 77 | // if (!marketProxyAddress.isContract()) { 78 | // revert NFTMarketPrivateSale_Proxy_Address_Is_Not_A_Contract(); 79 | // } 80 | DOMAIN_SEPARATOR = keccak256( 81 | abi.encode( 82 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 83 | keccak256(bytes(NAME)), 84 | // Incrementing the version can be used to invalidate previously signed messages. 85 | keccak256(bytes("1")), 86 | block.chainid, 87 | marketProxyAddress 88 | ) 89 | ); 90 | } 91 | 92 | /** 93 | * @notice Buy an NFT from a private sale. 94 | * @dev This API is deprecated and will be removed in the future, `buyFromPrivateSaleFor` should be used instead. 95 | * The seller signs a message approving the sale and then the buyer calls this function 96 | * with the `msg.value` equal to the agreed upon price. 97 | * If the seller is no longer the `ownerOf` this NFT or has removed approval for this contract, 98 | * attempts to purchase from private sale will revert. 99 | * @param nftContract The address of the NFT contract. 100 | * @param tokenId The ID of the NFT. 101 | * @param deadline The timestamp at which the offer to sell will expire. 102 | * @param v The v value of the EIP-712 signature. 103 | * @param r The r value of the EIP-712 signature. 104 | * @param s The s value of the EIP-712 signature. 105 | */ 106 | function buyFromPrivateSale( 107 | address nftContract, 108 | uint256 tokenId, 109 | uint256 deadline, 110 | uint8 v, 111 | bytes32 r, 112 | bytes32 s 113 | ) external payable { 114 | buyFromPrivateSaleFor(nftContract, tokenId, msg.value, deadline, v, r, s); 115 | } 116 | 117 | /** 118 | * @notice Buy an NFT from a private sale. 119 | * @dev The seller signs a message approving the sale and then the buyer calls this function 120 | * with the `amount` equal to the agreed upon price. 121 | * If the seller is no longer the `ownerOf` this NFT or has removed approval for this contract, 122 | * attempts to purchase from private sale will revert. 123 | * @dev `amount` - `msg.value` is withdrawn from the bidder's METH balance. 124 | * @param nftContract The address of the NFT contract. 125 | * @param tokenId The ID of the NFT. 126 | * @param amount The amount to buy for, if this is more than `msg.value` funds will be 127 | * withdrawn from your METH balance. 128 | * @param deadline The timestamp at which the offer to sell will expire. 129 | * @param v The v value of the EIP-712 signature. 130 | * @param r The r value of the EIP-712 signature. 131 | * @param s The s value of the EIP-712 signature. 132 | */ 133 | function buyFromPrivateSaleFor( 134 | address nftContract, 135 | uint256 tokenId, 136 | uint256 amount, 137 | uint256 deadline, 138 | uint8 v, 139 | bytes32 r, 140 | bytes32 s 141 | ) public payable nonReentrant { 142 | // now + 2 days cannot overflow 143 | unchecked { 144 | if (deadline < block.timestamp) { 145 | // The signed message from the seller has expired. 146 | revert NFTMarketPrivateSale_Sale_Expired(); 147 | } else if (deadline > block.timestamp + 2 days) { 148 | // Private sales typically expire in 24 hours, but 2 days is used here in order to ensure 149 | // that transactions do not fail due to a minor timezone error or similar during signing. 150 | 151 | // This prevents malicious actors from requesting signatures that never expire. 152 | revert NFTMarketPrivateSale_Can_Be_Offered_For_24Hrs_Max(); 153 | } 154 | } 155 | 156 | // Cancel the buyer's offer if there is one in order to free up their METH balance 157 | // even if they don't need the METH for this specific purchase. 158 | _cancelSendersOffer(address(nftContract), tokenId); 159 | 160 | if (amount > msg.value) { 161 | // Withdraw additional ETH required from their available METH balance. 162 | 163 | unchecked { 164 | // The if above ensures delta will not underflow 165 | meth.marketWithdrawFrom(msg.sender, amount - msg.value); 166 | } 167 | } else if (amount < msg.value) { 168 | // The terms of the sale cannot change, so if too much ETH is sent then something went wrong. 169 | revert NFTMarketPrivateSale_Too_Much_Value_Provided(); 170 | } 171 | 172 | // The seller must have the NFT in their wallet when this function is called, 173 | // otherwise the signature verification below will fail. 174 | address payable seller = payable(IERC721(nftContract).ownerOf(tokenId)); 175 | 176 | // Ensure that the offer can only be accepted once. 177 | if (privateSaleInvalidated[nftContract][tokenId][msg.sender][seller][amount][deadline]) { 178 | revert NFTMarketPrivateSale_Signature_Canceled_Or_Already_Claimed(); 179 | } 180 | privateSaleInvalidated[nftContract][tokenId][msg.sender][seller][amount][deadline] = true; 181 | 182 | // Scoping this block to avoid a stack too deep error 183 | { 184 | bytes32 digest = keccak256( 185 | abi.encodePacked( 186 | "\x19\x01", 187 | DOMAIN_SEPARATOR, 188 | keccak256(abi.encode(BUY_FROM_PRIVATE_SALE_TYPEHASH, nftContract, tokenId, msg.sender, amount, deadline)) 189 | ) 190 | ); 191 | 192 | // Revert if the signature is invalid, the terms are not as expected, or if the seller transferred the NFT. 193 | if (digest.recover(v, r, s) != seller) { 194 | revert NFTMarketPrivateSale_Signature_Verification_Failed(); 195 | } 196 | } 197 | 198 | // This should revert if the seller has not given the market contract approval. 199 | IERC721(nftContract).transferFrom(seller, msg.sender, tokenId); 200 | 201 | // Distribute revenue for this sale. 202 | (uint256 museeFee, uint256 creatorFee, uint256 ownerRev) = _distributeFunds(nftContract, tokenId, seller, amount); 203 | 204 | emit PrivateSaleFinalized(nftContract, tokenId, seller, msg.sender, museeFee, creatorFee, ownerRev, deadline); 205 | } 206 | 207 | /** 208 | * @notice This empty reserved space is put in place to allow future versions to add new 209 | * variables without shifting down storage in the inheritance chain. 210 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 211 | * @dev 1 slot was consumed by privateSaleInvalidated. 212 | */ 213 | uint256[999] private __gap; 214 | } 215 | -------------------------------------------------------------------------------- /mixins/OZ/EnumerableMap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | /** 4 | * From https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v3.4.2-solc-0.7 5 | * Modified to support solc 8. 6 | * Using this due to storage slot changes in OZ 4.* 7 | */ 8 | 9 | pragma solidity ^0.8.0; 10 | 11 | /** 12 | * @title Library for an enumerable mapping type. 13 | * @dev Library for managing an enumerable variant of Solidity's 14 | * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] 15 | * type. 16 | * 17 | * Maps have the following properties: 18 | * 19 | * - Entries are added, removed, and checked for existence in constant time 20 | * (O(1)). 21 | * - Entries are enumerated in O(n). No guarantees are made on the ordering. 22 | * 23 | * ``` 24 | * contract Example { 25 | * // Add the library methods 26 | * using EnumerableMap for EnumerableMap.UintToAddressMap; 27 | * 28 | * // Declare a set state variable 29 | * EnumerableMap.UintToAddressMap private myMap; 30 | * } 31 | * ``` 32 | * 33 | * As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are 34 | * supported. 35 | */ 36 | library EnumerableMap { 37 | // To implement this library for multiple types with as little code 38 | // repetition as possible, we write it in terms of a generic Map type with 39 | // bytes32 keys and values. 40 | // The Map implementation uses private functions, and user-facing 41 | // implementations (such as Uint256ToAddressMap) are just wrappers around 42 | // the underlying Map. 43 | // This means that we can only create new EnumerableMaps for types that fit 44 | // in bytes32. 45 | 46 | struct MapEntry { 47 | bytes32 _key; 48 | bytes32 _value; 49 | } 50 | 51 | struct Map { 52 | // Storage of map keys and values 53 | MapEntry[] _entries; 54 | // Position of the entry defined by a key in the `entries` array, plus 1 55 | // because index 0 means a key is not in the map. 56 | mapping(bytes32 => uint256) _indexes; 57 | } 58 | 59 | /** 60 | * @dev Adds a key-value pair to a map, or updates the value for an existing 61 | * key. O(1). 62 | * 63 | * Returns true if the key was added to the map, that is if it was not 64 | * already present. 65 | */ 66 | function _set( 67 | Map storage map, 68 | bytes32 key, 69 | bytes32 value 70 | ) private returns (bool) { 71 | // We read and store the key's index to prevent multiple reads from the same storage slot 72 | uint256 keyIndex = map._indexes[key]; 73 | 74 | if (keyIndex == 0) { 75 | // Equivalent to !contains(map, key) 76 | map._entries.push(MapEntry({ _key: key, _value: value })); 77 | // The entry is stored at length-1, but we add 1 to all indexes 78 | // and use 0 as a sentinel value 79 | map._indexes[key] = map._entries.length; 80 | return true; 81 | } else { 82 | unchecked { 83 | map._entries[keyIndex - 1]._value = value; 84 | } 85 | return false; 86 | } 87 | } 88 | 89 | /** 90 | * @dev Removes a key-value pair from a map. O(1). 91 | * 92 | * Returns true if the key was removed from the map, that is if it was present. 93 | */ 94 | function _remove(Map storage map, bytes32 key) private returns (bool) { 95 | // We read and store the key's index to prevent multiple reads from the same storage slot 96 | uint256 keyIndex = map._indexes[key]; 97 | 98 | if (keyIndex != 0) { 99 | unchecked { 100 | // Equivalent to contains(map, key) 101 | // To delete a key-value pair from the _entries array in O(1), we swap the entry to delete with the last one 102 | // in the array, and then remove the last entry (sometimes called as 'swap and pop'). 103 | // This modifies the order of the array, as noted in {at}. 104 | 105 | uint256 toDeleteIndex = keyIndex - 1; 106 | uint256 lastIndex = map._entries.length - 1; 107 | 108 | // When the entry to delete is the last one, the swap operation is unnecessary. However, since this occurs 109 | // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. 110 | 111 | MapEntry storage lastEntry = map._entries[lastIndex]; 112 | 113 | // Move the last entry to the index where the entry to delete is 114 | map._entries[toDeleteIndex] = lastEntry; 115 | // Update the index for the moved entry 116 | map._indexes[lastEntry._key] = toDeleteIndex + 1; // All indexes are 1-based 117 | } 118 | 119 | // Delete the slot where the moved entry was stored 120 | map._entries.pop(); 121 | 122 | // Delete the index for the deleted slot 123 | delete map._indexes[key]; 124 | 125 | return true; 126 | } else { 127 | return false; 128 | } 129 | } 130 | 131 | /** 132 | * @dev Returns true if the key is in the map. O(1). 133 | */ 134 | function _contains(Map storage map, bytes32 key) private view returns (bool) { 135 | return map._indexes[key] != 0; 136 | } 137 | 138 | /** 139 | * @dev Returns the number of key-value pairs in the map. O(1). 140 | */ 141 | function _length(Map storage map) private view returns (uint256) { 142 | return map._entries.length; 143 | } 144 | 145 | /** 146 | * @dev Returns the key-value pair stored at position `index` in the map. O(1). 147 | * 148 | * Note that there are no guarantees on the ordering of entries inside the 149 | * array, and it may change when more entries are added or removed. 150 | * 151 | * Requirements: 152 | * 153 | * - `index` must be strictly less than {length}. 154 | */ 155 | function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) { 156 | require(map._entries.length > index, "EnumerableMap: index out of bounds"); 157 | 158 | MapEntry storage entry = map._entries[index]; 159 | return (entry._key, entry._value); 160 | } 161 | 162 | /** 163 | * @dev Tries to returns the value associated with `key`. O(1). 164 | * Does not revert if `key` is not in the map. 165 | */ 166 | function _tryGet(Map storage map, bytes32 key) private view returns (bool, bytes32) { 167 | uint256 keyIndex = map._indexes[key]; 168 | if (keyIndex == 0) return (false, 0); // Equivalent to contains(map, key) 169 | return (true, map._entries[keyIndex - 1]._value); // All indexes are 1-based 170 | } 171 | 172 | /** 173 | * @dev Returns the value associated with `key`. O(1). 174 | * 175 | * Requirements: 176 | * 177 | * - `key` must be in the map. 178 | */ 179 | function _get(Map storage map, bytes32 key) private view returns (bytes32) { 180 | uint256 keyIndex = map._indexes[key]; 181 | require(keyIndex != 0, "EnumerableMap: nonexistent key"); // Equivalent to contains(map, key) 182 | unchecked { 183 | return map._entries[keyIndex - 1]._value; // All indexes are 1-based 184 | } 185 | } 186 | 187 | /** 188 | * @dev Same as {_get}, with a custom error message when `key` is not in the map. 189 | * 190 | * CAUTION: This function is deprecated because it requires allocating memory for the error 191 | * message unnecessarily. For custom revert reasons use {_tryGet}. 192 | */ 193 | function _get( 194 | Map storage map, 195 | bytes32 key, 196 | string memory errorMessage 197 | ) private view returns (bytes32) { 198 | uint256 keyIndex = map._indexes[key]; 199 | require(keyIndex != 0, errorMessage); // Equivalent to contains(map, key) 200 | unchecked { 201 | return map._entries[keyIndex - 1]._value; // All indexes are 1-based 202 | } 203 | } 204 | 205 | // UintToAddressMap 206 | 207 | struct UintToAddressMap { 208 | Map _inner; 209 | } 210 | 211 | /** 212 | * @dev Adds a key-value pair to a map, or updates the value for an existing 213 | * key. O(1). 214 | * 215 | * Returns true if the key was added to the map, that is if it was not 216 | * already present. 217 | */ 218 | function set( 219 | UintToAddressMap storage map, 220 | uint256 key, 221 | address value 222 | ) internal returns (bool) { 223 | return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); 224 | } 225 | 226 | /** 227 | * @dev Removes a value from a set. O(1). 228 | * 229 | * Returns true if the key was removed from the map, that is if it was present. 230 | */ 231 | function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { 232 | return _remove(map._inner, bytes32(key)); 233 | } 234 | 235 | /** 236 | * @dev Returns true if the key is in the map. O(1). 237 | */ 238 | function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { 239 | return _contains(map._inner, bytes32(key)); 240 | } 241 | 242 | /** 243 | * @dev Returns the number of elements in the map. O(1). 244 | */ 245 | function length(UintToAddressMap storage map) internal view returns (uint256) { 246 | return _length(map._inner); 247 | } 248 | 249 | /** 250 | * @dev Returns the element stored at position `index` in the set. O(1). 251 | * Note that there are no guarantees on the ordering of values inside the 252 | * array, and it may change when more values are added or removed. 253 | * 254 | * Requirements: 255 | * 256 | * - `index` must be strictly less than {length}. 257 | */ 258 | function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { 259 | (bytes32 key, bytes32 value) = _at(map._inner, index); 260 | return (uint256(key), address(uint160(uint256(value)))); 261 | } 262 | 263 | /** 264 | * @dev Tries to returns the value associated with `key`. O(1). 265 | * Does not revert if `key` is not in the map. 266 | * 267 | * _Available since v3.4._ 268 | */ 269 | function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { 270 | (bool success, bytes32 value) = _tryGet(map._inner, bytes32(key)); 271 | return (success, address(uint160(uint256(value)))); 272 | } 273 | 274 | /** 275 | * @dev Returns the value associated with `key`. O(1). 276 | * 277 | * Requirements: 278 | * 279 | * - `key` must be in the map. 280 | */ 281 | function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { 282 | return address(uint160(uint256(_get(map._inner, bytes32(key))))); 283 | } 284 | 285 | /** 286 | * @dev Same as {get}, with a custom error message when `key` is not in the map. 287 | * 288 | * CAUTION: This function is deprecated because it requires allocating memory for the error 289 | * message unnecessarily. For custom revert reasons use {tryGet}. 290 | */ 291 | function get( 292 | UintToAddressMap storage map, 293 | uint256 key, 294 | string memory errorMessage 295 | ) internal view returns (address) { 296 | return address(uint160(uint256(_get(map._inner, bytes32(key), errorMessage)))); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /PercentSplitETH.sol: -------------------------------------------------------------------------------- 1 | /* 2 | MUSEE Protocol 3 | */ 4 | 5 | // SPDX-License-Identifier: MIT OR Apache-2.0 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | import "./interfaces/IERC20Approve.sol"; 10 | import "./interfaces/IERC20IncreaseAllowance.sol"; 11 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 12 | 13 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 14 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 15 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 16 | import "@openzeppelin/contracts/proxy/Clones.sol"; 17 | import "./libraries/BytesLibrary.sol"; 18 | 19 | /** 20 | * @title Auto-forward ETH to a pre-determined list of addresses. 21 | * @notice Deploys contracts which auto-forwards any ETH sent to it to a list of recipients 22 | * considering their percent share of the payment received. 23 | * ERC-20 tokens are also supported and may be split on demand by calling `splitERC20Tokens`. 24 | * If another asset type is sent to this contract address such as an NFT, arbitrary calls may be made by one 25 | * of the split recipients in order to recover them. 26 | * @dev Uses create2 counterfactual addresses so that the destination is known from the terms of the split. 27 | */ 28 | contract PercentSplitETH is Initializable { 29 | using AddressUpgradeable for address payable; 30 | using AddressUpgradeable for address; 31 | using BytesLibrary for bytes; 32 | using SafeMath for uint256; 33 | 34 | /// @notice A representation of shares using 16-bits for efficient storage. 35 | /// @dev This is only used internally. 36 | struct ShareCompressed { 37 | address payable recipient; 38 | uint16 percentInBasisPoints; 39 | } 40 | 41 | /// @notice A representation of shares using 256-bits to ease integration. 42 | struct Share { 43 | address payable recipient; 44 | uint256 percentInBasisPoints; 45 | } 46 | 47 | ShareCompressed[] private _shares; 48 | 49 | uint256 private constant BASIS_POINTS = 10000; 50 | 51 | /** 52 | * @notice Emitted when an ERC20 token is transferred to a recipient through this split contract. 53 | * @param erc20Contract The address of the ERC20 token contract. 54 | * @param account The account which received payment. 55 | * @param amount The amount of ERC20 tokens sent to this recipient. 56 | */ 57 | event ERC20Transferred(address indexed erc20Contract, address indexed account, uint256 amount); 58 | /** 59 | * @notice Emitted when ETH is transferred to a recipient through this split contract. 60 | * @param account The account which received payment. 61 | * @param amount The amount of ETH payment sent to this recipient. 62 | */ 63 | event ETHTransferred(address indexed account, uint256 amount); 64 | /** 65 | * @notice Emitted when a new percent split contract is created from this factory. 66 | * @param contractAddress The address of the new percent split contract. 67 | */ 68 | event PercentSplitCreated(address indexed contractAddress); 69 | /** 70 | * @notice Emitted for each share of the split being defined. 71 | * @param recipient The address of the recipient when payment to the split is received. 72 | * @param percentInBasisPoints The percent of the payment received by the recipient, in basis points. 73 | */ 74 | event PercentSplitShare(address indexed recipient, uint256 percentInBasisPoints); 75 | 76 | /** 77 | * @dev Requires that the msg.sender is one of the recipients in this split. 78 | */ 79 | modifier onlyRecipient() { 80 | for (uint256 i = 0; i < _shares.length; ++i) { 81 | if (_shares[i].recipient == msg.sender) { 82 | _; 83 | return; 84 | } 85 | } 86 | revert("Split: Can only be called by one of the recipients"); 87 | } 88 | 89 | /** 90 | * @notice Called once to configure the contract after the initial deployment. 91 | * @dev This will be called by `createSplit` after deploying the proxy so it should never be called directly. 92 | * @param shares The list of recipients and their share of the payment for the template to use. 93 | */ 94 | function initialize(Share[] memory shares) external initializer { 95 | require(shares.length >= 2, "Split: Too few recipients"); 96 | require(shares.length <= 5, "Split: Too many recipients"); 97 | uint256 total; 98 | unchecked { 99 | // The array length cannot overflow 256 bits. 100 | for (uint256 i = 0; i < shares.length; ++i) { 101 | require(shares[i].percentInBasisPoints < BASIS_POINTS, "Split: Share must be less than 100%"); 102 | // Require above ensures total will not overflow. 103 | total += shares[i].percentInBasisPoints; 104 | _shares.push( 105 | ShareCompressed({ 106 | recipient: shares[i].recipient, 107 | percentInBasisPoints: uint16(shares[i].percentInBasisPoints) 108 | }) 109 | ); 110 | emit PercentSplitShare(shares[i].recipient, shares[i].percentInBasisPoints); 111 | } 112 | } 113 | require(total == BASIS_POINTS, "Split: Total amount must equal 100%"); 114 | } 115 | 116 | /** 117 | * @notice Forwards any ETH received to the recipients in this split. 118 | * @dev Each recipient increases the gas required to split 119 | * and contract recipients may significantly increase the gas required. 120 | */ 121 | receive() external payable { 122 | _splitETH(msg.value); 123 | } 124 | 125 | /** 126 | * @notice Creates a new minimal proxy contract and initializes it with the given split terms. 127 | * If the contract had already been created, its address is returned. 128 | * This must be called on the original implementation and not a proxy created previously. 129 | * @param shares The list of recipients and their share of the payment for this split. 130 | * @return splitInstance The contract address for the split contract created. 131 | */ 132 | function createSplit(Share[] memory shares) external returns (PercentSplitETH splitInstance) { 133 | bytes32 salt = keccak256(abi.encode(shares)); 134 | address clone = Clones.predictDeterministicAddress(address(this), salt); 135 | splitInstance = PercentSplitETH(payable(clone)); 136 | if (!clone.isContract()) { 137 | emit PercentSplitCreated(clone); 138 | Clones.cloneDeterministic(address(this), salt); 139 | splitInstance.initialize(shares); 140 | } 141 | } 142 | 143 | /** 144 | * @notice Allows the split recipients to make an arbitrary contract call. 145 | * @dev This is provided to allow recovering from unexpected scenarios, 146 | * such as receiving an NFT at this address. 147 | * 148 | * It will first attempt a fair split of ERC20 tokens before proceeding. 149 | * 150 | * This contract is built to split ETH payments. The ability to attempt to make other calls is here 151 | * just in case other assets were also sent so that they don't get locked forever in the contract. 152 | * @param target The address of the contract to call. 153 | * @param callData The data to send to the `target` contract. 154 | */ 155 | function proxyCall(address payable target, bytes memory callData) external onlyRecipient { 156 | require( 157 | !callData.startsWith(type(IERC20Approve).interfaceId) && 158 | !callData.startsWith(type(IERC20IncreaseAllowance).interfaceId), 159 | "Split: ERC20 tokens must be split" 160 | ); 161 | _splitERC20Tokens(IERC20(target)); 162 | target.functionCall(callData); 163 | } 164 | 165 | /** 166 | * @notice Allows any ETH stored by the contract to be split among recipients. 167 | * @dev Normally ETH is forwarded as it comes in, but a balance in this contract 168 | * is possible if it was sent before the contract was created or if self destruct was used. 169 | */ 170 | function splitETH() external { 171 | _splitETH(address(this).balance); 172 | } 173 | 174 | /** 175 | * @notice Anyone can call this function to split all available tokens at the provided address between the recipients. 176 | * @dev This contract is built to split ETH payments. The ability to attempt to split ERC20 tokens is here 177 | * just in case tokens were also sent so that they don't get locked forever in the contract. 178 | * @param erc20Contract The address of the ERC20 token contract to split tokens for. 179 | */ 180 | function splitERC20Tokens(IERC20 erc20Contract) external { 181 | require(_splitERC20Tokens(erc20Contract), "Split: ERC20 split failed"); 182 | } 183 | 184 | function _splitERC20Tokens(IERC20 erc20Contract) private returns (bool) { 185 | try erc20Contract.balanceOf(address(this)) returns (uint256 balance) { 186 | if (balance == 0) { 187 | return false; 188 | } 189 | uint256 amountToSend; 190 | uint256 totalSent; 191 | unchecked { 192 | for (uint256 i = _shares.length - 1; i != 0; i--) { 193 | ShareCompressed memory share = _shares[i]; 194 | bool success; 195 | (success, amountToSend) = balance.tryMul(share.percentInBasisPoints); 196 | if (!success) { 197 | return false; 198 | } 199 | amountToSend /= BASIS_POINTS; 200 | totalSent += amountToSend; 201 | try erc20Contract.transfer(share.recipient, amountToSend) { 202 | emit ERC20Transferred(address(erc20Contract), share.recipient, amountToSend); 203 | } catch { 204 | return false; 205 | } 206 | } 207 | // Favor the 1st recipient if there are any rounding issues 208 | amountToSend = balance - totalSent; 209 | } 210 | try erc20Contract.transfer(_shares[0].recipient, amountToSend) { 211 | emit ERC20Transferred(address(erc20Contract), _shares[0].recipient, amountToSend); 212 | } catch { 213 | return false; 214 | } 215 | return true; 216 | } catch { 217 | return false; 218 | } 219 | } 220 | 221 | function _splitETH(uint256 value) private { 222 | if (value != 0) { 223 | uint256 totalSent; 224 | uint256 amountToSend; 225 | unchecked { 226 | for (uint256 i = _shares.length - 1; i != 0; i--) { 227 | ShareCompressed memory share = _shares[i]; 228 | amountToSend = (value * share.percentInBasisPoints) / BASIS_POINTS; 229 | totalSent += amountToSend; 230 | share.recipient.sendValue(amountToSend); 231 | emit ETHTransferred(share.recipient, amountToSend); 232 | } 233 | // Favor the 1st recipient if there are any rounding issues 234 | amountToSend = value - totalSent; 235 | } 236 | _shares[0].recipient.sendValue(amountToSend); 237 | emit ETHTransferred(_shares[0].recipient, amountToSend); 238 | } 239 | } 240 | 241 | /** 242 | * @notice Returns a recipient's percent share in basis points. 243 | * @param index The index of the recipient to get the share of. 244 | * @return percentInBasisPoints The percent of the payment received by the recipient, in basis points. 245 | */ 246 | function getPercentInBasisPointsByIndex(uint256 index) external view returns (uint256 percentInBasisPoints) { 247 | percentInBasisPoints = _shares[index].percentInBasisPoints; 248 | } 249 | 250 | /** 251 | * @notice Returns the address for the proxy contract which would represent the given split terms. 252 | * @dev The contract may or may not already be deployed at the address returned. 253 | * Ensure that it is deployed before sending funds to this address. 254 | * @param shares The list of recipients and their share of the payment for this split. 255 | * @return splitInstance The contract address for the split contract created. 256 | */ 257 | function getPredictedSplitAddress(Share[] memory shares) external view returns (address splitInstance) { 258 | bytes32 salt = keccak256(abi.encode(shares)); 259 | splitInstance = Clones.predictDeterministicAddress(address(this), salt); 260 | } 261 | 262 | /** 263 | * @notice Returns how many recipients are part of this split. 264 | * @return length The number of recipients in this split. 265 | */ 266 | function getShareLength() external view returns (uint256 length) { 267 | length = _shares.length; 268 | } 269 | 270 | /** 271 | * @notice Returns a recipient in this split. 272 | * @param index The index of the recipient to get. 273 | * @return recipient The recipient at the given index. 274 | */ 275 | function getShareRecipientByIndex(uint256 index) external view returns (address payable recipient) { 276 | recipient = _shares[index].recipient; 277 | } 278 | 279 | /** 280 | * @notice Returns a tuple with the terms of this split. 281 | * @return shares The list of recipients and their share of the payment for this split. 282 | */ 283 | function getShares() external view returns (Share[] memory shares) { 284 | shares = new Share[](_shares.length); 285 | for (uint256 i = 0; i < shares.length; ++i) { 286 | shares[i] = Share({ recipient: _shares[i].recipient, percentInBasisPoints: _shares[i].percentInBasisPoints }); 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /mixins/NFT721Creator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./OZ/ERC721Upgradeable.sol"; 6 | import "../libraries/BytesLibrary.sol"; 7 | import "./NFT721ProxyCall.sol"; 8 | import "../interfaces/ITokenCreator.sol"; 9 | import "../libraries/AccountMigrationLibrary.sol"; 10 | import "./MuseeTreasuryNode.sol"; 11 | 12 | /** 13 | * @title Allows each token to be associated with a creator. 14 | * @notice Also manages the payment address for each NFT, allowing royalties to be split with collaborators. 15 | */ 16 | abstract contract NFT721Creator is MuseeTreasuryNode, ERC721Upgradeable, ITokenCreator, NFT721ProxyCall { 17 | using AccountMigrationLibrary for address; 18 | using BytesLibrary for bytes; 19 | 20 | /** 21 | * @notice Stores the creator address for each NFT. 22 | */ 23 | mapping(uint256 => address payable) private tokenIdToCreator; 24 | 25 | /** 26 | * @notice Stores an optional alternate address to receive creator revenue and royalty payments. 27 | * The target address may be a contract which could split or escrow payments. 28 | * @dev This is address(0) when not applicable and royalties should be sent to the creator instead. 29 | */ 30 | mapping(uint256 => address payable) private tokenIdToCreatorPaymentAddress; 31 | 32 | /** 33 | * @notice Emitted when the creator for an NFT is set. 34 | * @param fromCreator The original creator address for this NFT. 35 | * @param toCreator The new creator address for this NFT. 36 | * @param tokenId The token ID for the NFT which was updated. 37 | */ 38 | event TokenCreatorUpdated(address indexed fromCreator, address indexed toCreator, uint256 indexed tokenId); 39 | /** 40 | * @notice Emitted when the creator payment address for an NFT is set. 41 | * @param fromPaymentAddress The original creator payment address for this NFT. 42 | * @param toPaymentAddress The new creator payment address for this NFT. 43 | * @param tokenId The token ID for the NFT which was updated. 44 | */ 45 | event TokenCreatorPaymentAddressSet( 46 | address indexed fromPaymentAddress, 47 | address indexed toPaymentAddress, 48 | uint256 indexed tokenId 49 | ); 50 | /** 51 | * @notice Emitted when the creator for an NFT is changed through account migration. 52 | * @param tokenId The tokenId of the NFT which had the creator changed. 53 | * @param originalAddress The original creator address for this NFT. 54 | * @param newAddress The new creator address for this NFT. 55 | */ 56 | event NFTCreatorMigrated(uint256 indexed tokenId, address indexed originalAddress, address indexed newAddress); 57 | /** 58 | * @notice Emitted when the owner of an NFT is changed through account migration. 59 | * @param tokenId The tokenId of the NFT which had the owner changed. 60 | * @param originalAddress The original owner address for this NFT. 61 | * @param newAddress The new owner address for this NFT. 62 | */ 63 | event NFTOwnerMigrated(uint256 indexed tokenId, address indexed originalAddress, address indexed newAddress); 64 | /** 65 | * @notice Emitted when the payment address for an NFT is changed through account migration. 66 | * @param tokenId The tokenId of the NFT which had the payment address changed. 67 | * @param originalAddress The original recipient address for royalties that is being migrated. 68 | * @param newAddress The new recipient address for royalties. 69 | * @param originalPaymentAddress The original payment address for royalty payments. 70 | * @param newPaymentAddress The new payment address used to split royalty payments. 71 | */ 72 | event PaymentAddressMigrated( 73 | uint256 indexed tokenId, 74 | address indexed originalAddress, 75 | address indexed newAddress, 76 | address originalPaymentAddress, 77 | address newPaymentAddress 78 | ); 79 | 80 | /** 81 | * @notice Allows an NFT owner or creator and Musee to work together in order to update the creator 82 | * to a new account and/or transfer NFTs to that account. 83 | * @dev This will gracefully skip any NFTs that have been burned or transferred. 84 | * @param createdTokenIds The tokenIds of the NFTs which were created by the original address. 85 | * @param ownedTokenIds The tokenIds of the NFTs owned by the original address to be migrated to the new account. 86 | * @param originalAddress The original account address to be migrated. 87 | * @param newAddress The new address for the account. 88 | * @param signature Message `I authorize Musee to migrate my account to ${newAccount.address.toLowerCase()}` 89 | * signed by the original account. 90 | */ 91 | function adminAccountMigration( 92 | uint256[] calldata createdTokenIds, 93 | uint256[] calldata ownedTokenIds, 94 | address originalAddress, 95 | address payable newAddress, 96 | bytes calldata signature 97 | ) external onlyMuseeOperator { 98 | originalAddress.requireAuthorizedAccountMigration(newAddress, signature); 99 | unchecked { 100 | // The array length cannot overflow 256 bits. 101 | for (uint256 i = 0; i < ownedTokenIds.length; ++i) { 102 | uint256 tokenId = ownedTokenIds[i]; 103 | // Check that the token exists and still owned by the originalAddress 104 | // so that frontrunning a burn or transfer will not cause the entire tx to revert 105 | if (_exists(tokenId) && ownerOf(tokenId) == originalAddress) { 106 | _transfer(originalAddress, newAddress, tokenId); 107 | emit NFTOwnerMigrated(tokenId, originalAddress, newAddress); 108 | } 109 | } 110 | 111 | for (uint256 i = 0; i < createdTokenIds.length; ++i) { 112 | uint256 tokenId = createdTokenIds[i]; 113 | // The creator would be 0 if the token was burned before this call 114 | if (tokenIdToCreator[tokenId] != address(0)) { 115 | require( 116 | tokenIdToCreator[tokenId] == originalAddress, 117 | "NFT721Creator: Token was not created by the given address" 118 | ); 119 | _updateTokenCreator(tokenId, newAddress); 120 | emit NFTCreatorMigrated(tokenId, originalAddress, newAddress); 121 | } 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * @notice Allows a split recipient and Musee to work together in order to update the payment address 128 | * to a new account. 129 | * @param paymentAddressTokenIds The token IDs for the NFTs to have their payment address migrated. 130 | * @param paymentAddressFactory The contract which was used to generate the payment address being migrated. 131 | * @param paymentAddressCallData The original call data used to generate the payment address being migrated. 132 | * @param addressLocationInCallData The position where the account to migrate begins in the call data. 133 | * @param originalAddress The original account address to be migrated. 134 | * @param newAddress The new address for the account. 135 | * @param signature Message `I authorize Musee to migrate my account to ${newAccount.address.toLowerCase()}` 136 | * signed by the original account. 137 | */ 138 | function adminAccountMigrationForPaymentAddresses( 139 | uint256[] calldata paymentAddressTokenIds, 140 | address paymentAddressFactory, 141 | bytes calldata paymentAddressCallData, 142 | uint256 addressLocationInCallData, 143 | address originalAddress, 144 | address payable newAddress, 145 | bytes calldata signature 146 | ) external onlyMuseeOperator { 147 | originalAddress.requireAuthorizedAccountMigration(newAddress, signature); 148 | _adminAccountRecoveryForPaymentAddresses( 149 | paymentAddressTokenIds, 150 | paymentAddressFactory, 151 | paymentAddressCallData, 152 | addressLocationInCallData, 153 | originalAddress, 154 | newAddress 155 | ); 156 | } 157 | 158 | /** 159 | * @notice Allows the creator to burn if they currently own the NFT. 160 | * @param tokenId The tokenId of the NFT to be burned. 161 | */ 162 | function burn(uint256 tokenId) external { 163 | require(tokenIdToCreator[tokenId] == msg.sender, "NFT721Creator: Caller is not creator"); 164 | require(_isApprovedOrOwner(msg.sender, tokenId), "NFT721Creator: Caller is not owner nor approved"); 165 | _burn(tokenId); 166 | } 167 | 168 | /** 169 | * @dev Split into a second function to avoid stack too deep errors 170 | */ 171 | function _adminAccountRecoveryForPaymentAddresses( 172 | uint256[] calldata paymentAddressTokenIds, 173 | address paymentAddressFactory, 174 | bytes memory paymentAddressCallData, 175 | uint256 addressLocationInCallData, 176 | address originalAddress, 177 | address payable newAddress 178 | ) private { 179 | // Call the factory and get the originalPaymentAddress 180 | address payable originalPaymentAddress = _proxyCallAndReturnContractAddress( 181 | paymentAddressFactory, 182 | paymentAddressCallData 183 | ); 184 | 185 | // Confirm the original address and swap with the new address 186 | paymentAddressCallData.replaceAtIf(addressLocationInCallData, originalAddress, newAddress); 187 | 188 | // Call the factory and get the newPaymentAddress 189 | address payable newPaymentAddress = _proxyCallAndReturnContractAddress( 190 | paymentAddressFactory, 191 | paymentAddressCallData 192 | ); 193 | 194 | // For each token, confirm the expected payment address and then update to the new one 195 | unchecked { 196 | // The array length cannot overflow 256 bits. 197 | for (uint256 i = 0; i < paymentAddressTokenIds.length; ++i) { 198 | uint256 tokenId = paymentAddressTokenIds[i]; 199 | require( 200 | tokenIdToCreatorPaymentAddress[tokenId] == originalPaymentAddress, 201 | "NFT721Creator: Payment address is not the expected value" 202 | ); 203 | 204 | _setTokenCreatorPaymentAddress(tokenId, newPaymentAddress); 205 | emit PaymentAddressMigrated(tokenId, originalAddress, newAddress, originalPaymentAddress, newPaymentAddress); 206 | } 207 | } 208 | } 209 | 210 | /** 211 | * @dev Remove the creator and payment address records when burned. 212 | */ 213 | function _burn(uint256 tokenId) internal virtual override { 214 | delete tokenIdToCreator[tokenId]; 215 | delete tokenIdToCreatorPaymentAddress[tokenId]; 216 | 217 | // Delete the NFT details. 218 | super._burn(tokenId); 219 | } 220 | 221 | /** 222 | * @dev Allow setting a different address to send payments to for both primary sale revenue 223 | * and secondary sales royalties. 224 | */ 225 | function _setTokenCreatorPaymentAddress(uint256 tokenId, address payable tokenCreatorPaymentAddress) internal { 226 | emit TokenCreatorPaymentAddressSet(tokenIdToCreatorPaymentAddress[tokenId], tokenCreatorPaymentAddress, tokenId); 227 | tokenIdToCreatorPaymentAddress[tokenId] = tokenCreatorPaymentAddress; 228 | } 229 | 230 | function _updateTokenCreator(uint256 tokenId, address payable creator) internal { 231 | emit TokenCreatorUpdated(tokenIdToCreator[tokenId], creator, tokenId); 232 | 233 | tokenIdToCreator[tokenId] = creator; 234 | } 235 | 236 | /** 237 | * @notice Returns the payment address for a given tokenId. 238 | * @dev If an alternate address was not defined, the creator is returned instead. 239 | * @param tokenId The tokenId of the NFT to get the payment address for. 240 | * @return tokenCreatorPaymentAddress The address to which royalties should be sent for this NFT. 241 | */ 242 | function getTokenCreatorPaymentAddress(uint256 tokenId) 243 | public 244 | view 245 | returns (address payable tokenCreatorPaymentAddress) 246 | { 247 | tokenCreatorPaymentAddress = tokenIdToCreatorPaymentAddress[tokenId]; 248 | if (tokenCreatorPaymentAddress == address(0)) { 249 | tokenCreatorPaymentAddress = tokenIdToCreator[tokenId]; 250 | } 251 | } 252 | 253 | /** 254 | * @inheritdoc ERC165 255 | * @dev Checks the ITokenCreator interface. 256 | */ 257 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 258 | if (interfaceId == type(ITokenCreator).interfaceId) { 259 | return true; 260 | } 261 | return super.supportsInterface(interfaceId); 262 | } 263 | 264 | /** 265 | * @notice Returns the creator's address for a given tokenId. 266 | * @param tokenId The tokenId of the NFT to get the creator for. 267 | * @return creator The creator's address for the given tokenId. 268 | */ 269 | function tokenCreator(uint256 tokenId) external view override returns (address payable creator) { 270 | creator = tokenIdToCreator[tokenId]; 271 | } 272 | 273 | /** 274 | * @notice This empty reserved space is put in place to allow future versions to add new 275 | * variables without shifting down storage in the inheritance chain. 276 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 277 | * @dev 1 slot was using with the addition of `tokenIdToCreatorPaymentAddress`. 278 | */ 279 | uint256[999] private __gap; 280 | } 281 | -------------------------------------------------------------------------------- /mixins/NFTMarketBuyPrice.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 7 | 8 | import "./NFTMarketCore.sol"; 9 | import "./NFTMarketFees.sol"; 10 | 11 | /// @param buyPrice The current buy price set for this NFT. 12 | error NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price(uint256 buyPrice); 13 | error NFTMarketBuyPrice_Cannot_Buy_Unset_Price(); 14 | error NFTMarketBuyPrice_Cannot_Cancel_Unset_Price(); 15 | /// @param owner The current owner of this NFT. 16 | error NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price(address owner); 17 | /// @param owner The current owner of this NFT. 18 | error NFTMarketBuyPrice_Only_Owner_Can_Set_Price(address owner); 19 | error NFTMarketBuyPrice_Price_Already_Set(); 20 | error NFTMarketBuyPrice_Price_Too_High(); 21 | /// @param seller The current owner of this NFT. 22 | error NFTMarketBuyPrice_Seller_Mismatch(address seller); 23 | error NFTMarketBuyPrice_Too_Low_Value_Provided(); 24 | /** 25 | * @title Allows sellers to set a buy price of their NFTs that may be accepted and instantly transferred to the buyer. 26 | * @notice NFTs with a buy price set are escrowed in the market contract. 27 | */ 28 | abstract contract NFTMarketBuyPrice is NFTMarketCore, NFTMarketFees { 29 | using AddressUpgradeable for address payable; 30 | 31 | /// @notice Stores the buy price details for a specific NFT. 32 | /// @dev The struct is packed into a single slot to optimize gas. 33 | struct BuyPrice { 34 | /// @notice The current owner of this NFT which set a buy price. 35 | /// @dev A zero price is acceptable so a non-zero address determines whether a price has been set. 36 | address payable seller; 37 | /// @notice The current buy price set for this NFT. 38 | uint96 price; 39 | } 40 | 41 | /// @notice Stores the current buy price for each NFT. 42 | mapping(address => mapping(uint256 => BuyPrice)) private nftContractToTokenIdToBuyPrice; 43 | 44 | /** 45 | * @notice Emitted when an NFT is bought by accepting the buy price, 46 | * indicating that the NFT has been transferred and revenue from the sale distributed. 47 | * @dev The total buy price that was accepted is `museeFee` + `creatorFee` + `ownerRev`. 48 | * @param nftContract The address of the NFT contract. 49 | * @param tokenId The id of the NFT. 50 | * @param buyer The address of the collector that purchased the NFT using `buy`. 51 | * @param seller The address of the seller which originally set the buy price. 52 | * @param museeFee The amount of ETH that was sent to Musee for this sale. 53 | * @param creatorFee The amount of ETH that was sent to the creator for this sale. 54 | * @param ownerRev The amount of ETH that was sent to the owner for this sale. 55 | */ 56 | 57 | event BuyPriceCanceled(address indexed nftContract, uint256 indexed tokenId); 58 | /** 59 | * @notice Emitted when a buy price is invalidated due to other market activity. 60 | * @dev This occurs when the buy price is no longer eligible to be accepted, 61 | * e.g. when a bid is placed in an auction for this NFT. 62 | * @param nftContract The address of the NFT contract. 63 | * @param tokenId The id of the NFT. 64 | */ 65 | event BuyPriceInvalidated(address indexed nftContract, uint256 indexed tokenId); 66 | /** 67 | * @notice Emitted when a buy price is set by the owner of an NFT. 68 | * @dev The NFT is transferred into the market contract for escrow unless it was already escrowed, 69 | * e.g. for auction listing. 70 | * @param nftContract The address of the NFT contract. 71 | * @param tokenId The id of the NFT. 72 | * @param seller The address of the NFT owner which set the buy price. 73 | * @param price The price of the NFT. 74 | */ 75 | event BuyPriceAccepted( 76 | address indexed nftContract, 77 | uint256 indexed tokenId, 78 | address indexed seller, 79 | address buyer, 80 | uint256 museeFee, 81 | uint256 creatorFee, 82 | uint256 ownerRev 83 | ); 84 | /** 85 | * @notice Emitted when the buy price is removed by the owner of an NFT. 86 | * @dev The NFT is transferred back to the owner unless it's still escrowed for another market tool, 87 | * e.g. listed for sale in an auction. 88 | * @param nftContract The address of the NFT contract. 89 | * @param tokenId The id of the NFT. 90 | */ 91 | event BuyPriceSet(address indexed nftContract, uint256 indexed tokenId, address indexed seller, uint256 price); 92 | 93 | /** 94 | * @notice Buy the NFT at the set buy price. 95 | * `msg.value` must be <= `maxPrice` and any delta will be taken from the account's available METH balance. 96 | * @dev `maxPrice` protects the buyer in case a the price is increased but allows the transaction to continue 97 | * when the price is reduced (and any surplus funds provided are refunded). 98 | * @param nftContract The address of the NFT contract. 99 | * @param tokenId The id of the NFT. 100 | * @param maxPrice The maximum price to pay for the NFT. 101 | */ 102 | function buy( 103 | address nftContract, 104 | uint256 tokenId, 105 | uint256 maxPrice 106 | ) external payable { 107 | BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; 108 | if (buyPrice.price > maxPrice) { 109 | revert NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price(buyPrice.price); 110 | } else if (buyPrice.seller == address(0)) { 111 | revert NFTMarketBuyPrice_Cannot_Buy_Unset_Price(); 112 | } 113 | 114 | _buy(nftContract, tokenId); 115 | } 116 | 117 | /** 118 | * @notice Removes the buy price set for an NFT. 119 | * @dev The NFT is transferred back to the owner unless it's still escrowed for another market tool, 120 | * e.g. listed for sale in an auction. 121 | * @param nftContract The address of the NFT contract. 122 | * @param tokenId The id of the NFT. 123 | */ 124 | function cancelBuyPrice(address nftContract, uint256 tokenId) external nonReentrant { 125 | address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; 126 | if (seller == address(0)) { 127 | // This check is redundant with the next one, but done in order to provide a more clear error message. 128 | revert NFTMarketBuyPrice_Cannot_Cancel_Unset_Price(); 129 | } else if (seller != msg.sender) { 130 | revert NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price(seller); 131 | } 132 | 133 | // Remove the buy price 134 | delete nftContractToTokenIdToBuyPrice[nftContract][tokenId]; 135 | 136 | // Transfer the NFT back to the owner if it is not listed in auction. 137 | _transferFromEscrowIfAvailable(nftContract, tokenId, msg.sender); 138 | 139 | emit BuyPriceCanceled(nftContract, tokenId); 140 | } 141 | 142 | /** 143 | * @notice Sets the buy price for an NFT and escrows it in the market contract. 144 | * A 0 price is acceptable and valid price you can set, enabling a giveaway to the first collector that calls `buy`. 145 | * @dev If there is an offer for this amount or higher, that will be accepted instead of setting a buy price. 146 | * @param nftContract The address of the NFT contract. 147 | * @param tokenId The id of the NFT. 148 | * @param price The price at which someone could buy this NFT. 149 | */ 150 | function setBuyPrice( 151 | address nftContract, 152 | uint256 tokenId, 153 | uint256 price 154 | ) external nonReentrant { 155 | if (price < 0.01 ether) { 156 | // min start price - 0.01 ETH 157 | revert NFTMarketBuyPrice_Too_Low_Value_Provided(); 158 | } 159 | 160 | // If there is a valid offer at this price or higher, accept that instead. 161 | if (_autoAcceptOffer(nftContract, tokenId, price)) { 162 | return; 163 | } 164 | 165 | if (price > type(uint96).max) { 166 | // This ensures that no data is lost when storing the price as `uint96`. 167 | revert NFTMarketBuyPrice_Price_Too_High(); 168 | } 169 | 170 | BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; 171 | address seller = buyPrice.seller; 172 | 173 | if (buyPrice.price == price && seller != address(0)) { 174 | revert NFTMarketBuyPrice_Price_Already_Set(); 175 | } 176 | 177 | // Store the new price for this NFT. 178 | buyPrice.price = uint96(price); 179 | 180 | if (seller == address(0)) { 181 | // Transfer the NFT into escrow, if it's already in escrow confirm the `msg.sender` is the owner. 182 | _transferToEscrow(nftContract, tokenId); 183 | 184 | // The price was not previously set for this NFT, store the seller. 185 | buyPrice.seller = payable(msg.sender); 186 | } else if (seller != msg.sender) { 187 | // Buy price was previously set by a different user 188 | revert NFTMarketBuyPrice_Only_Owner_Can_Set_Price(seller); 189 | } 190 | 191 | emit BuyPriceSet(nftContract, tokenId, msg.sender, price); 192 | } 193 | 194 | /** 195 | * @notice If there is a buy price at this price or lower, accept that and return true. 196 | */ 197 | function _autoAcceptBuyPrice( 198 | address nftContract, 199 | uint256 tokenId, 200 | uint256 maxPrice 201 | ) internal override returns (bool) { 202 | BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; 203 | if (buyPrice.seller == address(0) || buyPrice.price > maxPrice) { 204 | // No buy price was found, or the price is too high. 205 | return false; 206 | } 207 | 208 | _buy(nftContract, tokenId); 209 | return true; 210 | } 211 | 212 | /** 213 | * @inheritdoc NFTMarketCore 214 | * @dev Invalidates the buy price on a auction start, if one is found. 215 | */ 216 | function _beforeAuctionStarted(address nftContract, uint256 tokenId) internal virtual override { 217 | BuyPrice storage buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; 218 | if (buyPrice.seller != address(0)) { 219 | // A buy price was set for this NFT, invalidate it. 220 | _invalidateBuyPrice(nftContract, tokenId); 221 | } 222 | super._beforeAuctionStarted(nftContract, tokenId); 223 | } 224 | 225 | /** 226 | * @notice Process the purchase of an NFT at the current buy price. 227 | * @dev The caller must confirm that the seller != address(0) before calling this function. 228 | */ 229 | function _buy(address nftContract, uint256 tokenId) private nonReentrant { 230 | BuyPrice memory buyPrice = nftContractToTokenIdToBuyPrice[nftContract][tokenId]; 231 | 232 | // Remove the buy now price 233 | delete nftContractToTokenIdToBuyPrice[nftContract][tokenId]; 234 | 235 | // Cancel the buyer's offer if there is one in order to free up their METH balance 236 | // even if they don't need the METH for this specific purchase. 237 | _cancelSendersOffer(nftContract, tokenId); 238 | 239 | if (buyPrice.price > msg.value) { 240 | // Withdraw additional ETH required from their available METH balance. 241 | 242 | unchecked { 243 | // The if above ensures delta will not underflow. 244 | uint256 delta = buyPrice.price - msg.value; 245 | // Withdraw ETH from the buyer's account in the METH token contract, 246 | // making the ETH available for `_distributeFunds` below. 247 | meth.marketWithdrawFrom(msg.sender, delta); 248 | } 249 | } else if (buyPrice.price < msg.value) { 250 | // Return any surplus funds to the buyer. 251 | 252 | unchecked { 253 | // The if above ensures this will not underflow 254 | payable(msg.sender).sendValue(msg.value - buyPrice.price); 255 | } 256 | } 257 | 258 | // Transfer the NFT to the buyer. 259 | // The seller was already authorized when the buyPrice was set originally set. 260 | _transferFromEscrow(nftContract, tokenId, msg.sender, address(0)); 261 | 262 | // Distribute revenue for this sale. 263 | (uint256 museeFee, uint256 creatorFee, uint256 ownerRev) = _distributeFunds( 264 | nftContract, 265 | tokenId, 266 | buyPrice.seller, 267 | buyPrice.price 268 | ); 269 | 270 | emit BuyPriceAccepted(nftContract, tokenId, buyPrice.seller, msg.sender, museeFee, creatorFee, ownerRev); 271 | } 272 | 273 | /** 274 | * @notice Clear a buy price and emit BuyPriceInvalidated. 275 | * @dev The caller must confirm the buy price is set before calling this function. 276 | */ 277 | function _invalidateBuyPrice(address nftContract, uint256 tokenId) private { 278 | delete nftContractToTokenIdToBuyPrice[nftContract][tokenId]; 279 | emit BuyPriceInvalidated(nftContract, tokenId); 280 | } 281 | 282 | /** 283 | * @inheritdoc NFTMarketCore 284 | * @dev Invalidates the buy price if one is found before transferring the NFT. 285 | * This will revert if there is a buy price set but the `authorizeSeller` is not the owner. 286 | */ 287 | function _transferFromEscrow( 288 | address nftContract, 289 | uint256 tokenId, 290 | address recipient, 291 | address authorizeSeller 292 | ) internal virtual override { 293 | address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; 294 | if (seller != address(0)) { 295 | // A buy price was set for this NFT. 296 | // `authorizeSeller != address(0) &&` could be added when other mixins use this flow. 297 | // ATM that additional check would never return false. 298 | if (seller != authorizeSeller) { 299 | // When there is a buy price set, the `buyPrice.seller` is the owner of the NFT. 300 | revert NFTMarketBuyPrice_Seller_Mismatch(seller); 301 | } 302 | // The seller authorization has been confirmed. 303 | authorizeSeller = address(0); 304 | 305 | // Invalidate the buy price as the NFT will no longer be in escrow. 306 | _invalidateBuyPrice(nftContract, tokenId); 307 | } 308 | 309 | super._transferFromEscrow(nftContract, tokenId, recipient, authorizeSeller); 310 | } 311 | 312 | /** 313 | * @inheritdoc NFTMarketCore 314 | * @dev Checks if there is a buy price set, if not then allow the transfer to proceed. 315 | */ 316 | function _transferFromEscrowIfAvailable( 317 | address nftContract, 318 | uint256 tokenId, 319 | address recipient 320 | ) internal virtual override { 321 | address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; 322 | if (seller == address(0)) { 323 | // A buy price has been set for this NFT so it should remain in escrow. 324 | super._transferFromEscrowIfAvailable(nftContract, tokenId, recipient); 325 | } 326 | } 327 | 328 | /** 329 | * @inheritdoc NFTMarketCore 330 | * @dev Checks if the NFT is already in escrow for buy now. 331 | */ 332 | function _transferToEscrow(address nftContract, uint256 tokenId) internal virtual override { 333 | address seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; 334 | if (seller == address(0)) { 335 | // The NFT is not in escrow for buy now. 336 | super._transferToEscrow(nftContract, tokenId); 337 | } else if (seller != msg.sender) { 338 | // When there is a buy price set, the `seller` is the owner of the NFT. 339 | revert NFTMarketBuyPrice_Seller_Mismatch(seller); 340 | } 341 | } 342 | 343 | /** 344 | * @notice Returns the buy price details for an NFT if one is available. 345 | * @dev If no price is found, seller will be address(0) and price will be max uint256. 346 | * @param nftContract The address of the NFT contract. 347 | * @param tokenId The id of the NFT. 348 | * @return seller The address of the owner that listed a buy price for this NFT. 349 | * Returns `address(0)` if there is no buy price set for this NFT. 350 | * @return price The price of the NFT. 351 | * Returns `0` if there is no buy price set for this NFT. 352 | */ 353 | function getBuyPrice(address nftContract, uint256 tokenId) external view returns (address seller, uint256 price) { 354 | seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; 355 | if (seller == address(0)) { 356 | return (seller, type(uint256).max); 357 | } 358 | price = nftContractToTokenIdToBuyPrice[nftContract][tokenId].price; 359 | } 360 | 361 | /** 362 | * @inheritdoc NFTMarketCore 363 | * @dev Returns the seller if there is a buy price set for this NFT, otherwise 364 | * bubbles the call up for other considerations. 365 | */ 366 | function _getSellerFor(address nftContract, uint256 tokenId) 367 | internal 368 | view 369 | virtual 370 | override 371 | returns (address payable seller) 372 | { 373 | seller = nftContractToTokenIdToBuyPrice[nftContract][tokenId].seller; 374 | if (seller == address(0)) { 375 | seller = super._getSellerFor(nftContract, tokenId); 376 | } 377 | } 378 | 379 | /** 380 | * @notice This empty reserved space is put in place to allow future versions to add new 381 | * variables without shifting down storage in the inheritance chain. 382 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 383 | */ 384 | uint256[1000] private __gap; 385 | } 386 | 387 | 388 | 389 | /*╔═════════════════════════════════════════════════════════╗ 390 | ║ BUY NOW Functionality is implemented here! ║ 391 | ╚═════════════════════════════════════════════════════════╝*/ -------------------------------------------------------------------------------- /mixins/OZ/ERC721Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // solhint-disable 3 | 4 | /** 5 | * From https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/v3.4.0/contracts/token/ERC721/ERC721Upgradeable.sol 6 | * Modified in order to: 7 | * - make `_tokenURIs` internal instead of private. 8 | * - replace ERC165Upgradeable with ERC165. 9 | * - set the name/symbol with constants. 10 | */ 11 | 12 | pragma solidity ^0.8.0; 13 | 14 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 15 | import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; 16 | import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; 17 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 18 | 19 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 20 | import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; 21 | import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; 22 | import "./EnumerableMap.sol"; 23 | import "@openzeppelin/contracts/utils/Strings.sol"; 24 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 25 | 26 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 27 | import "../ERC165UpgradeableGap.sol"; 28 | 29 | /** 30 | * @title ERC721 Non-Fungible Token Standard basic implementation 31 | * @dev see https://eips.ethereum.org/EIPS/eip-721 32 | */ 33 | contract ERC721Upgradeable is 34 | Initializable, 35 | ContextUpgradeable, 36 | ERC165UpgradeableGap, 37 | ERC165, 38 | IERC721, 39 | IERC721Metadata, 40 | IERC721Enumerable 41 | { 42 | using AddressUpgradeable for address; 43 | using EnumerableSet for EnumerableSet.UintSet; 44 | using EnumerableMap for EnumerableMap.UintToAddressMap; 45 | using Strings for uint256; 46 | 47 | // Mapping from holder address to their (enumerable) set of owned tokens 48 | mapping(address => EnumerableSet.UintSet) private _holderTokens; 49 | 50 | // Enumerable mapping from token ids to their owners 51 | EnumerableMap.UintToAddressMap private _tokenOwners; 52 | 53 | // Mapping from token ID to approved address 54 | mapping(uint256 => address) private _tokenApprovals; 55 | 56 | // Mapping from owner to operator approvals 57 | mapping(address => mapping(address => bool)) private _operatorApprovals; 58 | 59 | /** 60 | * @dev Removing old unused variables in an upgrade safe way. Was: 61 | * string private __gap_was_name; 62 | * string private __gap_was_symbol; 63 | */ 64 | uint256[2] private __gap_was_metadata; 65 | 66 | // Optional mapping for token URIs 67 | mapping(uint256 => string) internal _tokenURIs; 68 | 69 | // Base URI 70 | string private _baseURI; 71 | 72 | /** 73 | * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. 74 | */ 75 | function __ERC721_init() internal onlyInitializing { 76 | __Context_init_unchained(); 77 | } 78 | 79 | /** 80 | * @inheritdoc ERC165 81 | * @dev Checks the ERC721 interfaces. 82 | */ 83 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 84 | if ( 85 | interfaceId == type(IERC721).interfaceId || 86 | interfaceId == type(IERC721Metadata).interfaceId || 87 | interfaceId == type(IERC721Enumerable).interfaceId 88 | ) { 89 | return true; 90 | } 91 | return super.supportsInterface(interfaceId); 92 | } 93 | 94 | /** 95 | * @dev See {IERC721-balanceOf}. 96 | */ 97 | function balanceOf(address owner) public view override returns (uint256) { 98 | require(owner != address(0), "ERC721: balance query for the zero address"); 99 | 100 | return _holderTokens[owner].length(); 101 | } 102 | 103 | /** 104 | * @dev See {IERC721-ownerOf}. 105 | */ 106 | function ownerOf(uint256 tokenId) public view override returns (address) { 107 | return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token"); 108 | } 109 | 110 | /** 111 | * @dev See {IERC721Metadata-name}. 112 | */ 113 | function name() public pure virtual override returns (string memory) { 114 | return "Musee"; 115 | } 116 | 117 | /** 118 | * @dev See {IERC721Metadata-symbol}. 119 | */ 120 | function symbol() public pure virtual override returns (string memory) { 121 | return "MUSEE"; 122 | } 123 | 124 | /** 125 | * @dev See {IERC721Metadata-tokenURI}. 126 | */ 127 | function tokenURI(uint256 tokenId) public view override returns (string memory) { 128 | require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); 129 | 130 | string memory _tokenURI = _tokenURIs[tokenId]; 131 | 132 | // If there is no base URI, return the token URI. 133 | if (bytes(_baseURI).length == 0) { 134 | return _tokenURI; 135 | } 136 | // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). 137 | if (bytes(_tokenURI).length != 0) { 138 | return string(abi.encodePacked(_baseURI, _tokenURI)); 139 | } 140 | // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI. 141 | return string(abi.encodePacked(_baseURI, tokenId.toString())); 142 | } 143 | 144 | /** 145 | * @dev Returns the base URI set via {_setBaseURI}. This will be 146 | * automatically added as a prefix in {tokenURI} to each token's URI, or 147 | * to the token ID if no specific URI is set for that token ID. 148 | */ 149 | function baseURI() public view returns (string memory) { 150 | return _baseURI; 151 | } 152 | 153 | /** 154 | * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. 155 | */ 156 | function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) { 157 | return _holderTokens[owner].at(index); 158 | } 159 | 160 | /** 161 | * @dev See {IERC721Enumerable-totalSupply}. 162 | */ 163 | function totalSupply() public view override returns (uint256) { 164 | // _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds 165 | return _tokenOwners.length(); 166 | } 167 | 168 | /** 169 | * @dev See {IERC721Enumerable-tokenByIndex}. 170 | */ 171 | function tokenByIndex(uint256 index) public view override returns (uint256) { 172 | (uint256 tokenId, ) = _tokenOwners.at(index); 173 | return tokenId; 174 | } 175 | 176 | /** 177 | * @dev See {IERC721-approve}. 178 | */ 179 | function approve(address to, uint256 tokenId) public virtual override { 180 | address owner = ownerOf(tokenId); 181 | require(to != owner, "ERC721: approval to current owner"); 182 | 183 | require( 184 | _msgSender() == owner || isApprovedForAll(owner, _msgSender()), 185 | "ERC721: approve caller is not owner nor approved for all" 186 | ); 187 | 188 | _approve(to, tokenId); 189 | } 190 | 191 | /** 192 | * @dev See {IERC721-getApproved}. 193 | */ 194 | function getApproved(uint256 tokenId) public view override returns (address) { 195 | require(_exists(tokenId), "ERC721: approved query for nonexistent token"); 196 | 197 | return _tokenApprovals[tokenId]; 198 | } 199 | 200 | /** 201 | * @dev See {IERC721-setApprovalForAll}. 202 | */ 203 | function setApprovalForAll(address operator, bool approved) public virtual override { 204 | require(operator != _msgSender(), "ERC721: approve to caller"); 205 | 206 | _operatorApprovals[_msgSender()][operator] = approved; 207 | emit ApprovalForAll(_msgSender(), operator, approved); 208 | } 209 | 210 | /** 211 | * @dev See {IERC721-isApprovedForAll}. 212 | */ 213 | function isApprovedForAll(address owner, address operator) public view override returns (bool) { 214 | return _operatorApprovals[owner][operator]; 215 | } 216 | 217 | /** 218 | * @dev See {IERC721-transferFrom}. 219 | */ 220 | function transferFrom( 221 | address from, 222 | address to, 223 | uint256 tokenId 224 | ) public virtual override { 225 | //solhint-disable-next-line max-line-length 226 | require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); 227 | 228 | _transfer(from, to, tokenId); 229 | } 230 | 231 | /** 232 | * @dev See {IERC721-safeTransferFrom}. 233 | */ 234 | function safeTransferFrom( 235 | address from, 236 | address to, 237 | uint256 tokenId 238 | ) public virtual override { 239 | safeTransferFrom(from, to, tokenId, ""); 240 | } 241 | 242 | /** 243 | * @dev See {IERC721-safeTransferFrom}. 244 | */ 245 | function safeTransferFrom( 246 | address from, 247 | address to, 248 | uint256 tokenId, 249 | bytes memory _data 250 | ) public virtual override { 251 | require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); 252 | _safeTransfer(from, to, tokenId, _data); 253 | } 254 | 255 | /** 256 | * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients 257 | * are aware of the ERC721 protocol to prevent tokens from being forever locked. 258 | * 259 | * `_data` is additional data, it has no specified format and it is sent in call to `to`. 260 | * 261 | * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. 262 | * implement alternative mechanisms to perform token transfer, such as signature-based. 263 | * 264 | * Requirements: 265 | * 266 | * - `from` cannot be the zero address. 267 | * - `to` cannot be the zero address. 268 | * - `tokenId` token must exist and be owned by `from`. 269 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 270 | * 271 | * Emits a {Transfer} event. 272 | */ 273 | function _safeTransfer( 274 | address from, 275 | address to, 276 | uint256 tokenId, 277 | bytes memory _data 278 | ) internal virtual { 279 | _transfer(from, to, tokenId); 280 | require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); 281 | } 282 | 283 | /** 284 | * @dev Returns whether `tokenId` exists. 285 | * 286 | * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. 287 | * 288 | * Tokens start existing when they are minted (`_mint`), 289 | * and stop existing when they are burned (`_burn`). 290 | */ 291 | function _exists(uint256 tokenId) internal view returns (bool) { 292 | return _tokenOwners.contains(tokenId); 293 | } 294 | 295 | /** 296 | * @dev Returns whether `spender` is allowed to manage `tokenId`. 297 | * 298 | * Requirements: 299 | * 300 | * - `tokenId` must exist. 301 | */ 302 | function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { 303 | require(_exists(tokenId), "ERC721: operator query for nonexistent token"); 304 | address owner = ownerOf(tokenId); 305 | return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); 306 | } 307 | 308 | /** 309 | * @dev Safely mints `tokenId` and transfers it to `to`. 310 | * 311 | * Requirements: 312 | d* 313 | * - `tokenId` must not exist. 314 | * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. 315 | * 316 | * Emits a {Transfer} event. 317 | */ 318 | function _safeMint(address to, uint256 tokenId) internal virtual { 319 | _safeMint(to, tokenId, ""); 320 | } 321 | 322 | /** 323 | * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is 324 | * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. 325 | */ 326 | function _safeMint( 327 | address to, 328 | uint256 tokenId, 329 | bytes memory _data 330 | ) internal virtual { 331 | _mint(to, tokenId); 332 | require( 333 | _checkOnERC721Received(address(0), to, tokenId, _data), 334 | "ERC721: transfer to non ERC721Receiver implementer" 335 | ); 336 | } 337 | 338 | /** 339 | * @dev Mints `tokenId` and transfers it to `to`. 340 | * 341 | * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible 342 | * 343 | * Requirements: 344 | * 345 | * - `tokenId` must not exist. 346 | * - `to` cannot be the zero address. 347 | * 348 | * Emits a {Transfer} event. 349 | */ 350 | function _mint(address to, uint256 tokenId) internal virtual { 351 | require(to != address(0), "ERC721: mint to the zero address"); 352 | require(!_exists(tokenId), "ERC721: token already minted"); 353 | 354 | _beforeTokenTransfer(address(0), to, tokenId); 355 | 356 | _holderTokens[to].add(tokenId); 357 | 358 | _tokenOwners.set(tokenId, to); 359 | 360 | emit Transfer(address(0), to, tokenId); 361 | } 362 | 363 | /** 364 | * @dev Destroys `tokenId`. 365 | * The approval is cleared when the token is burned. 366 | * 367 | * Requirements: 368 | * 369 | * - `tokenId` must exist. 370 | * 371 | * Emits a {Transfer} event. 372 | */ 373 | function _burn(uint256 tokenId) internal virtual { 374 | address owner = ownerOf(tokenId); 375 | 376 | _beforeTokenTransfer(owner, address(0), tokenId); 377 | 378 | // Clear approvals 379 | _approve(address(0), tokenId); 380 | 381 | // Clear metadata (if any) 382 | if (bytes(_tokenURIs[tokenId]).length != 0) { 383 | delete _tokenURIs[tokenId]; 384 | } 385 | 386 | _holderTokens[owner].remove(tokenId); 387 | 388 | _tokenOwners.remove(tokenId); 389 | 390 | emit Transfer(owner, address(0), tokenId); 391 | } 392 | 393 | /** 394 | * @dev Transfers `tokenId` from `from` to `to`. 395 | * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. 396 | * 397 | * Requirements: 398 | * 399 | * - `to` cannot be the zero address. 400 | * - `tokenId` token must be owned by `from`. 401 | * 402 | * Emits a {Transfer} event. 403 | */ 404 | function _transfer( 405 | address from, 406 | address to, 407 | uint256 tokenId 408 | ) internal virtual { 409 | require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); 410 | require(to != address(0), "ERC721: transfer to the zero address"); 411 | 412 | _beforeTokenTransfer(from, to, tokenId); 413 | 414 | // Clear approvals from the previous owner 415 | _approve(address(0), tokenId); 416 | 417 | _holderTokens[from].remove(tokenId); 418 | _holderTokens[to].add(tokenId); 419 | 420 | _tokenOwners.set(tokenId, to); 421 | 422 | emit Transfer(from, to, tokenId); 423 | } 424 | 425 | /** 426 | * @dev Sets `_tokenURI` as the tokenURI of `tokenId`. 427 | * 428 | * Requirements: 429 | * 430 | * - `tokenId` must exist. 431 | */ 432 | function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { 433 | require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token"); 434 | _tokenURIs[tokenId] = _tokenURI; 435 | } 436 | 437 | /** 438 | * @dev Internal function to set the base URI for all token IDs. It is 439 | * automatically added as a prefix to the value returned in {tokenURI}, 440 | * or to the token ID if {tokenURI} is empty. 441 | */ 442 | function _setBaseURI(string memory baseURI_) internal virtual { 443 | _baseURI = baseURI_; 444 | } 445 | 446 | /** 447 | * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. 448 | * The call is not executed if the target address is not a contract. 449 | * 450 | * @param from address representing the previous owner of the given token ID 451 | * @param to target address that will receive the tokens 452 | * @param tokenId uint256 ID of the token to be transferred 453 | * @param _data bytes optional data to send along with the call 454 | * @return bool whether the call correctly returned the expected magic value 455 | */ 456 | function _checkOnERC721Received( 457 | address from, 458 | address to, 459 | uint256 tokenId, 460 | bytes memory _data 461 | ) private returns (bool) { 462 | if (!to.isContract()) { 463 | return true; 464 | } 465 | bytes memory returndata = to.functionCall( 466 | abi.encodeWithSelector(IERC721Receiver(to).onERC721Received.selector, _msgSender(), from, tokenId, _data), 467 | "ERC721: transfer to non ERC721Receiver implementer" 468 | ); 469 | bytes4 retval = abi.decode(returndata, (bytes4)); 470 | return (retval == type(IERC721Receiver).interfaceId); 471 | } 472 | 473 | function _approve(address to, uint256 tokenId) private { 474 | _tokenApprovals[tokenId] = to; 475 | emit Approval(ownerOf(tokenId), to, tokenId); 476 | } 477 | 478 | /** 479 | * @dev Hook that is called before any token transfer. This includes minting 480 | * and burning. 481 | * 482 | * Calling conditions: 483 | * 484 | * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be 485 | * transferred to `to`. 486 | * - When `from` is zero, `tokenId` will be minted for `to`. 487 | * - When `to` is zero, ``from``'s `tokenId` will be burned. 488 | * - `from` cannot be the zero address. 489 | * - `to` cannot be the zero address. 490 | * 491 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 492 | */ 493 | function _beforeTokenTransfer( 494 | address from, 495 | address to, 496 | uint256 tokenId 497 | ) internal virtual {} 498 | 499 | /** 500 | * @notice This empty reserved space is put in place to allow future versions to add new 501 | * variables without shifting down storage in the inheritance chain. 502 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 503 | */ 504 | uint256[41] private __gap; 505 | } 506 | -------------------------------------------------------------------------------- /mixins/NFTMarketOffer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 7 | 8 | import "./MuseeTreasuryNode.sol"; 9 | import "./NFTMarketCore.sol"; 10 | import "./NFTMarketFees.sol"; 11 | 12 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 13 | 14 | error NFTMarketOffer_Cannot_Be_Made_While_In_Auction(); 15 | /// @param currentOfferAmount The current highest offer available for this NFT. 16 | error NFTMarketOffer_Offer_Below_Min_Amount(uint256 currentOfferAmount); 17 | /// @param expiry The time at which the offer had expired. 18 | error NFTMarketOffer_Offer_Expired(uint256 expiry); 19 | /// @param currentOfferFrom The address of the collector which has made the current highest offer. 20 | error NFTMarketOffer_Offer_From_Does_Not_Match(address currentOfferFrom); 21 | /// @param minOfferAmount The minimum amount that must be offered in order for it to be accepted. 22 | error NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount(uint256 minOfferAmount); 23 | error NFTMarketOffer_Reason_Required(); 24 | error NFTMarketOffer_Provided_Contract_And_TokenId_Count_Must_Match(); 25 | 26 | /** 27 | * @title Allows collectors to make an offer for an NFT, valid for 24-25 hours. 28 | * @notice Funds are escrowed in the METH ERC-20 token contract. 29 | */ 30 | abstract contract NFTMarketOffer is MuseeTreasuryNode, NFTMarketCore, ReentrancyGuardUpgradeable, NFTMarketFees { 31 | using AddressUpgradeable for address; 32 | 33 | /// @notice Stores offer details for a specific NFT. 34 | struct Offer { 35 | // Slot 1: When increasing an offer, only this slot is updated. 36 | /// @notice The expiration timestamp of when this offer expires. 37 | uint32 expiration; 38 | /// @notice The amount, in wei, of the highest offer. 39 | uint96 amount; 40 | // 128 bits are available in slot 1 41 | 42 | // Slot 2: When the buyer changes, both slots need updating 43 | /// @notice The address of the collector who made this offer. 44 | address buyer; 45 | } 46 | 47 | /// @notice Stores the highest offer for each NFT. 48 | mapping(address => mapping(uint256 => Offer)) private nftContractToIdToOffer; 49 | 50 | /** 51 | * @notice Emitted when an offer is accepted, 52 | * indicating that the NFT has been transferred and revenue from the sale distributed. 53 | * @dev The accepted total offer amount is `museeFee` + `creatorFee` + `ownerRev`. 54 | * @param nftContract The address of the NFT contract. 55 | * @param tokenId The id of the NFT. 56 | * @param buyer The address of the collector that made the offer which was accepted. 57 | * @param seller The address of the seller which accepted the offer. 58 | * @param museeFee The amount of ETH that was sent to Musee for this sale. 59 | * @param creatorFee The amount of ETH that was sent to the creator for this sale. 60 | * @param ownerRev The amount of ETH that was sent to the owner for this sale. 61 | */ 62 | event OfferAccepted( 63 | address indexed nftContract, 64 | uint256 indexed tokenId, 65 | address indexed buyer, 66 | address seller, 67 | uint256 museeFee, 68 | uint256 creatorFee, 69 | uint256 ownerRev 70 | ); 71 | /** 72 | * @notice Emitted when an offer is canceled by a Musee admin. 73 | * @dev This should only be used for extreme cases such as DMCA takedown requests. 74 | * @param nftContract The address of the NFT contract. 75 | * @param tokenId The id of the NFT. 76 | * @param reason The reason for the cancellation (a required field). 77 | */ 78 | event OfferCanceledByAdmin(address indexed nftContract, uint256 indexed tokenId, string reason); 79 | /** 80 | * @notice Emitted when an offer is invalidated due to other market activity. 81 | * When this occurs, the collector which made the offer has their METH balance unlocked 82 | * and the funds are available to place other offers or to be withdrawn. 83 | * @dev This occurs when the offer is no longer eligible to be accepted, 84 | * e.g. when a bid is placed in an auction for this NFT. 85 | * @param nftContract The address of the NFT contract. 86 | * @param tokenId The id of the NFT. 87 | */ 88 | event OfferInvalidated(address indexed nftContract, uint256 indexed tokenId); 89 | /** 90 | * @notice Emitted when an offer is made. 91 | * @dev The `amount` of the offer is locked in the METH ERC-20 contract, guaranteeing that the funds 92 | * remain available until the `expiration` date. 93 | * @param nftContract The address of the NFT contract. 94 | * @param tokenId The id of the NFT. 95 | * @param buyer The address of the collector that made the offer to buy this NFT. 96 | * @param amount The amount, in wei, of the offer. 97 | * @param expiration The expiration timestamp for the offer. 98 | */ 99 | event OfferMade( 100 | address indexed nftContract, 101 | uint256 indexed tokenId, 102 | address indexed buyer, 103 | uint256 amount, 104 | uint256 expiration 105 | ); 106 | 107 | /** 108 | * @notice Accept the highest offer for an NFT. 109 | * @dev The offer must not be expired and the NFT owned + approved by the seller or 110 | * available in the market contract's escrow. 111 | * @param nftContract The address of the NFT contract. 112 | * @param tokenId The id of the NFT. 113 | * @param offerFrom The address of the collector that you wish to sell to. 114 | * If the current highest offer is not from this user, the transaction will revert. 115 | * This could happen if a last minute offer was made by another collector, 116 | * and would require the seller to try accepting again. 117 | * @param minAmount The minimum value of the highest offer for it to be accepted. 118 | * If the value is less than this amount, the transaction will revert. 119 | * This could happen if the original offer expires and is replaced with a smaller offer. 120 | */ 121 | function acceptOffer( 122 | address nftContract, 123 | uint256 tokenId, 124 | address offerFrom, 125 | uint256 minAmount 126 | ) external nonReentrant { 127 | Offer storage offer = nftContractToIdToOffer[nftContract][tokenId]; 128 | // Validate offer expiry and amount 129 | if (offer.expiration < block.timestamp) { 130 | revert NFTMarketOffer_Offer_Expired(offer.expiration); 131 | } else if (offer.amount < minAmount) { 132 | revert NFTMarketOffer_Offer_Below_Min_Amount(offer.amount); 133 | } 134 | // Validate the buyer 135 | if (offer.buyer != offerFrom) { 136 | revert NFTMarketOffer_Offer_From_Does_Not_Match(offer.buyer); 137 | } 138 | 139 | _acceptOffer(nftContract, tokenId); 140 | } 141 | 142 | /** 143 | * @notice Allows Musee to cancel offers. 144 | * This will unlock the funds in the METH ERC-20 contract for the highest offer 145 | * and prevent the offer from being accepted. 146 | * @dev This should only be used for extreme cases such as DMCA takedown requests. 147 | * @param nftContracts The addresses of the NFT contracts to cancel. This must be the same length as `tokenIds`. 148 | * @param tokenIds The ids of the NFTs to cancel. This must be the same length as `nftContracts`. 149 | * @param reason The reason for the cancellation (a required field). 150 | */ 151 | function adminCancelOffers( 152 | address[] calldata nftContracts, 153 | uint256[] calldata tokenIds, 154 | string calldata reason 155 | ) external onlyMuseeAdmin nonReentrant { 156 | if (bytes(reason).length == 0) { 157 | revert NFTMarketOffer_Reason_Required(); 158 | } 159 | if (nftContracts.length != tokenIds.length) { 160 | revert NFTMarketOffer_Provided_Contract_And_TokenId_Count_Must_Match(); 161 | } 162 | 163 | // The array length cannot overflow 256 bits 164 | unchecked { 165 | for (uint256 i = 0; i < nftContracts.length; ++i) { 166 | Offer memory offer = nftContractToIdToOffer[nftContracts[i]][tokenIds[i]]; 167 | delete nftContractToIdToOffer[nftContracts[i]][tokenIds[i]]; 168 | 169 | if (offer.expiration >= block.timestamp) { 170 | // Unlock from escrow and emit an event only if the offer is still active 171 | meth.marketUnlockFor(offer.buyer, offer.expiration, offer.amount); 172 | emit OfferCanceledByAdmin(nftContracts[i], tokenIds[i], reason); 173 | } 174 | // Else continue on so the rest of the batch transaction can process successfully 175 | } 176 | } 177 | } 178 | 179 | /** 180 | * @notice Make an offer for any NFT which is valid for 24-25 hours. 181 | * The funds will be locked in the METH token contract and become available once the offer is outbid or has expired. 182 | * @dev An offer may be made for an NFT before it is minted, although we generally not recommend you do that. 183 | * If there is a buy price set at this price or lower, that will be accepted instead of making an offer. 184 | * `msg.value` must be <= `amount` and any delta will be taken from the account's available METH balance. 185 | * @param nftContract The address of the NFT contract. 186 | * @param tokenId The id of the NFT. 187 | * @param amount The amount to offer for this NFT. 188 | * @return expiration The timestamp for when this offer will expire. 189 | * This is provided as a return value in case another contract would like to leverage this information, 190 | * user's should refer to the expiration in the `OfferMade` event log. 191 | * If the buy price is accepted instead, `0` is returned as the expiration since that's n/a. 192 | */ 193 | function makeOffer( 194 | address nftContract, 195 | uint256 tokenId, 196 | uint256 amount 197 | ) external payable returns (uint256 expiration) { 198 | // If there is a buy price set at this price or lower, accept that instead. 199 | if (_autoAcceptBuyPrice(nftContract, tokenId, amount)) { 200 | // If the buy price is accepted, `0` is returned as the expiration since that's n/a. 201 | return 0; 202 | } 203 | 204 | if (_isInActiveAuction(nftContract, tokenId)) { 205 | revert NFTMarketOffer_Cannot_Be_Made_While_In_Auction(); 206 | } 207 | 208 | Offer storage offer = nftContractToIdToOffer[nftContract][tokenId]; 209 | 210 | if (offer.expiration < block.timestamp) { 211 | // This is a new offer for the NFT (no other offer found or the previous offer expired) 212 | 213 | // Lock the offer amount in METH until the offer expires in 24-25 hours. 214 | expiration = meth.marketLockupFor{ value: msg.value }(msg.sender, amount); 215 | } else { 216 | // A previous offer exists and has not expired 217 | 218 | uint256 minIncrement = _getMinIncrement(offer.amount); 219 | if (amount < minIncrement) { 220 | // A non-trivial increase in price is required to avoid sniping 221 | revert NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount(minIncrement); 222 | } 223 | 224 | // Unlock the previous offer so that the METH tokens are available for other offers or to transfer / withdraw 225 | // and lock the new offer amount in METH until the offer expires in 24-25 hours. 226 | expiration = meth.marketChangeLockup{ value: msg.value }( 227 | offer.buyer, 228 | offer.expiration, 229 | offer.amount, 230 | msg.sender, 231 | amount 232 | ); 233 | } 234 | 235 | // Record offer details 236 | offer.buyer = msg.sender; 237 | // The METH contract guarantees that the expiration fits into 32 bits. 238 | offer.expiration = uint32(expiration); 239 | // `amount` is capped by the ETH provided, which cannot realistically overflow 96 bits. 240 | offer.amount = uint96(amount); 241 | 242 | emit OfferMade(nftContract, tokenId, msg.sender, amount, expiration); 243 | } 244 | 245 | /** 246 | * @notice Accept the highest offer for an NFT from the `msg.sender` account. 247 | * The NFT will be transferred to the buyer and revenue from the sale will be distributed. 248 | * @dev The caller must validate the expiry and amount before calling this helper. 249 | * This may invalidate other market tools, such as clearing the buy price if set. 250 | */ 251 | function _acceptOffer(address nftContract, uint256 tokenId) private { 252 | Offer memory offer = nftContractToIdToOffer[nftContract][tokenId]; 253 | 254 | // Remove offer 255 | delete nftContractToIdToOffer[nftContract][tokenId]; 256 | // Withdraw ETH from the buyer's account in the METH token contract. 257 | meth.marketWithdrawLocked(offer.buyer, offer.expiration, offer.amount); 258 | 259 | // Transfer the NFT to the buyer. 260 | try 261 | IERC721(nftContract).transferFrom(msg.sender, offer.buyer, tokenId) // solhint-disable-next-line no-empty-blocks 262 | { 263 | // NFT was in the seller's wallet so the transfer is complete. 264 | } catch { 265 | // If the transfer fails then attempt to transfer from escrow instead. 266 | // This should revert if `msg.sender` is not the owner of this NFT. 267 | _transferFromEscrow(nftContract, tokenId, offer.buyer, msg.sender); 268 | } 269 | 270 | // Distribute revenue for this sale leveraging the ETH received from the METH contract in the line above. 271 | (uint256 museeFee, uint256 creatorFee, uint256 ownerRev) = _distributeFunds( 272 | nftContract, 273 | tokenId, 274 | payable(msg.sender), 275 | offer.amount 276 | ); 277 | 278 | emit OfferAccepted(nftContract, tokenId, offer.buyer, msg.sender, museeFee, creatorFee, ownerRev); 279 | } 280 | 281 | /** 282 | * @inheritdoc NFTMarketCore 283 | * @dev Invalidates the highest offer when an auction is kicked off, if one is found. 284 | */ 285 | function _beforeAuctionStarted(address nftContract, uint256 tokenId) internal virtual override { 286 | _invalidateOffer(nftContract, tokenId); 287 | super._beforeAuctionStarted(nftContract, tokenId); 288 | } 289 | 290 | /** 291 | * @inheritdoc NFTMarketCore 292 | */ 293 | function _autoAcceptOffer( 294 | address nftContract, 295 | uint256 tokenId, 296 | uint256 minAmount 297 | ) internal override returns (bool) { 298 | Offer storage offer = nftContractToIdToOffer[nftContract][tokenId]; 299 | if (offer.expiration < block.timestamp || offer.amount < minAmount) { 300 | // No offer found, the most recent offer is now expired, or the highest offer is below the minimum amount. 301 | return false; 302 | } 303 | 304 | _acceptOffer(nftContract, tokenId); 305 | return true; 306 | } 307 | 308 | /** 309 | * @inheritdoc NFTMarketCore 310 | */ 311 | function _cancelSendersOffer(address nftContract, uint256 tokenId) internal override { 312 | Offer storage offer = nftContractToIdToOffer[nftContract][tokenId]; 313 | if (offer.buyer == msg.sender) { 314 | _invalidateOffer(nftContract, tokenId); 315 | } 316 | } 317 | 318 | /** 319 | * @notice Invalidates the offer and frees ETH from escrow, if the offer has not already expired. 320 | * @dev Offers are not invalidated when the NFT is purchased by accepting the buy price unless it 321 | * was purchased by the same user. 322 | * The user which just purchased the NFT may have buyer's remorse and promptly decide they want a fast exit, 323 | * accepting a small loss to limit their exposure. 324 | */ 325 | function _invalidateOffer(address nftContract, uint256 tokenId) private { 326 | if (nftContractToIdToOffer[nftContract][tokenId].expiration >= block.timestamp) { 327 | // An offer was found and it has not already expired 328 | Offer memory offer = nftContractToIdToOffer[nftContract][tokenId]; 329 | 330 | // Remove offer 331 | delete nftContractToIdToOffer[nftContract][tokenId]; 332 | 333 | // Unlock the offer so that the METH tokens are available for other offers or to transfer / withdraw 334 | meth.marketUnlockFor(offer.buyer, offer.expiration, offer.amount); 335 | 336 | emit OfferInvalidated(nftContract, tokenId); 337 | } 338 | } 339 | 340 | /** 341 | * @notice Returns the minimum amount a collector must offer for this NFT in order for the offer to be valid. 342 | * @dev Offers for this NFT which are less than this value will revert. 343 | * Once the previous offer has expired smaller offers can be made. 344 | * @param nftContract The address of the NFT contract. 345 | * @param tokenId The id of the NFT. 346 | * @return minimum The minimum amount that must be offered for this NFT. 347 | */ 348 | function getMinOfferAmount(address nftContract, uint256 tokenId) external view returns (uint256 minimum) { 349 | Offer storage offer = nftContractToIdToOffer[nftContract][tokenId]; 350 | if (offer.expiration >= block.timestamp) { 351 | return _getMinIncrement(offer.amount); 352 | } 353 | // Absolute min is anything > 0 354 | return 1; 355 | } 356 | 357 | /** 358 | * @notice Returns details about the current highest offer for an NFT. 359 | * @dev Default values are returned if there is no offer or the offer has expired. 360 | * @param nftContract The address of the NFT contract. 361 | * @param tokenId The id of the NFT. 362 | * @return buyer The address of the buyer that made the current highest offer. 363 | * Returns `address(0)` if there is no offer or the most recent offer has expired. 364 | * @return expiration The timestamp that the current highest offer expires. 365 | * Returns `0` if there is no offer or the most recent offer has expired. 366 | * @return amount The amount being offered for this NFT. 367 | * Returns `0` if there is no offer or the most recent offer has expired. 368 | */ 369 | function getOffer(address nftContract, uint256 tokenId) 370 | external 371 | view 372 | returns ( 373 | address buyer, 374 | uint256 expiration, 375 | uint256 amount 376 | ) 377 | { 378 | Offer storage offer = nftContractToIdToOffer[nftContract][tokenId]; 379 | if (offer.expiration < block.timestamp) { 380 | // Offer not found or has expired 381 | return (address(0), 0, 0); 382 | } 383 | 384 | // An offer was found and it has not yet expired. 385 | return (offer.buyer, offer.expiration, offer.amount); 386 | } 387 | 388 | /** 389 | * @notice This empty reserved space is put in place to allow future versions to add new 390 | * variables without shifting down storage in the inheritance chain. 391 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 392 | */ 393 | uint256[1000] private __gap; 394 | } 395 | --------------------------------------------------------------------------------