Skip to content

Commit

Permalink
OpenEditionERC721 flat platform fee (#614)
Browse files Browse the repository at this point in the history
* OpenEditionERC721 with flat fee

* flat fee support OE

* fix tests
  • Loading branch information
kumaryash90 authored Jan 26, 2024
1 parent da53846 commit effd208
Show file tree
Hide file tree
Showing 14 changed files with 2,242 additions and 0 deletions.
290 changes: 290 additions & 0 deletions contracts/prebuilts/open-edition/OpenEditionERC721FlatFee.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;

/// @author thirdweb

// $$\ $$\ $$\ $$\ $$\
// $$ | $$ | \__| $$ | $$ |
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/

// ========== External imports ==========

import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol";

import "../../eip/queryable/ERC721AQueryableUpgradeable.sol";

// ========== Internal imports ==========

import "../../external-deps/openzeppelin/metatx/ERC2771ContextUpgradeable.sol";
import "../../lib/CurrencyTransferLib.sol";

// ========== Features ==========

import "../../extension/Multicall.sol";
import "../../extension/ContractMetadata.sol";
import "../../extension/Royalty.sol";
import "../../extension/PrimarySale.sol";
import "../../extension/Ownable.sol";
import "../../extension/SharedMetadata.sol";
import "../../extension/PermissionsEnumerable.sol";
import "../../extension/Drop.sol";
import "../../extension/PlatformFee.sol";

contract OpenEditionERC721FlatFee is
Initializable,
ContractMetadata,
PlatformFee,
Royalty,
PrimarySale,
Ownable,
SharedMetadata,
PermissionsEnumerable,
Drop,
ERC2771ContextUpgradeable,
Multicall,
ERC721AQueryableUpgradeable
{
using StringsUpgradeable for uint256;

/*///////////////////////////////////////////////////////////////
State variables
//////////////////////////////////////////////////////////////*/

/// @dev Only transfers to or from TRANSFER_ROLE holders are valid, when transfers are restricted.
bytes32 private transferRole;
/// @dev Only MINTER_ROLE holders can update the shared metadata of tokens.
bytes32 private minterRole;

/// @dev Max bps in the thirdweb system.
uint256 private constant MAX_BPS = 10_000;

/*///////////////////////////////////////////////////////////////
Constructor + initializer logic
//////////////////////////////////////////////////////////////*/

constructor() initializer {}

/// @dev Initializes the contract, like a constructor.
function initialize(
address _defaultAdmin,
string memory _name,
string memory _symbol,
string memory _contractURI,
address[] memory _trustedForwarders,
address _saleRecipient,
address _royaltyRecipient,
uint128 _royaltyBps,
uint128 _platformFeeBps,
address _platformFeeRecipient
) external initializerERC721A initializer {
bytes32 _transferRole = keccak256("TRANSFER_ROLE");
bytes32 _minterRole = keccak256("MINTER_ROLE");

// Initialize inherited contracts, most base-like -> most derived.
__ERC2771Context_init(_trustedForwarders);
__ERC721A_init(_name, _symbol);

_setupContractURI(_contractURI);
_setupOwner(_defaultAdmin);

_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
_setupRole(_minterRole, _defaultAdmin);
_setupRole(_transferRole, _defaultAdmin);
_setupRole(_transferRole, address(0));

_setupPlatformFeeInfo(_platformFeeRecipient, _platformFeeBps);
_setupDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps);
_setupPrimarySaleRecipient(_saleRecipient);

transferRole = _transferRole;
minterRole = _minterRole;
}

/*///////////////////////////////////////////////////////////////
ERC 165 / 721 / 2981 logic
//////////////////////////////////////////////////////////////*/

/// @dev Returns the URI for a given tokenId.
function tokenURI(
uint256 _tokenId
) public view virtual override(ERC721AUpgradeable, IERC721AUpgradeable) returns (string memory) {
if (!_exists(_tokenId)) {
revert("!ID");
}

return _getURIFromSharedMetadata(_tokenId);
}

/// @dev See ERC 165
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC721AUpgradeable, IERC165, IERC721AUpgradeable) returns (bool) {
return super.supportsInterface(interfaceId) || type(IERC2981Upgradeable).interfaceId == interfaceId;
}

/// @dev The start token ID for the contract.
function _startTokenId() internal pure override returns (uint256) {
return 1;
}

function startTokenId() public pure returns (uint256) {
return _startTokenId();
}

/*///////////////////////////////////////////////////////////////
Internal functions
//////////////////////////////////////////////////////////////*/

/// @dev Collects and distributes the primary sale value of NFTs being claimed.
function _collectPriceOnClaim(
address _primarySaleRecipient,
uint256 _quantityToClaim,
address _currency,
uint256 _pricePerToken
) internal override {
if (_pricePerToken == 0) {
require(msg.value == 0, "!Value");
return;
}

uint256 totalPrice = _quantityToClaim * _pricePerToken;
uint256 platformFees;
address platformFeeRecipient;

if (getPlatformFeeType() == IPlatformFee.PlatformFeeType.Flat) {
(platformFeeRecipient, platformFees) = getFlatPlatformFeeInfo();
} else {
(address recipient, uint16 platformFeeBps) = getPlatformFeeInfo();
platformFeeRecipient = recipient;
platformFees = ((totalPrice * platformFeeBps) / MAX_BPS);
}
require(totalPrice >= platformFees, "price less than platform fee");

bool validMsgValue;
if (_currency == CurrencyTransferLib.NATIVE_TOKEN) {
validMsgValue = msg.value == totalPrice;
} else {
validMsgValue = msg.value == 0;
}
require(validMsgValue, "!V");

address saleRecipient = _primarySaleRecipient == address(0) ? primarySaleRecipient() : _primarySaleRecipient;

CurrencyTransferLib.transferCurrency(_currency, _msgSender(), platformFeeRecipient, platformFees);
CurrencyTransferLib.transferCurrency(_currency, _msgSender(), saleRecipient, totalPrice - platformFees);
}

/// @dev Transfers the NFTs being claimed.
function _transferTokensOnClaim(
address _to,
uint256 _quantityBeingClaimed
) internal override returns (uint256 startTokenId_) {
startTokenId_ = _nextTokenId();
_safeMint(_to, _quantityBeingClaimed);
}

/// @dev Checks whether primary sale recipient can be set in the given execution context.
function _canSetPrimarySaleRecipient() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/// @dev Checks whether owner can be set in the given execution context.
function _canSetOwner() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/// @dev Checks whether royalty info can be set in the given execution context.
function _canSetRoyaltyInfo() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/// @dev Checks whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/// @dev Checks whether platform fee info can be set in the given execution context.
function _canSetClaimConditions() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/// @dev Returns whether the shared metadata of tokens can be set in the given execution context.
function _canSetSharedMetadata() internal view virtual override returns (bool) {
return hasRole(minterRole, _msgSender());
}

/// @dev Checks whether platform fee info can be set in the given execution context.
function _canSetPlatformFeeInfo() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
}

/*///////////////////////////////////////////////////////////////
Miscellaneous
//////////////////////////////////////////////////////////////*/

/**
* Returns the total amount of tokens minted in the contract.
*/
function totalMinted() external view returns (uint256) {
unchecked {
return _nextTokenId() - _startTokenId();
}
}

/// @dev The tokenId of the next NFT that will be minted / lazy minted.
function nextTokenIdToMint() external view returns (uint256) {
return _nextTokenId();
}

/// @dev The next token ID of the NFT that can be claimed.
function nextTokenIdToClaim() external view returns (uint256) {
return _nextTokenId();
}

/// @dev Burns `tokenId`. See {ERC721-_burn}.
function burn(uint256 tokenId) external virtual {
// note: ERC721AUpgradeable's `_burn(uint256,bool)` internally checks for token approvals.
_burn(tokenId, true);
}

/// @dev See {ERC721-_beforeTokenTransfer}.
function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId_,
uint256 quantity
) internal virtual override {
super._beforeTokenTransfers(from, to, startTokenId_, quantity);

// if transfer is restricted on the contract, we still want to allow burning and minting
if (!hasRole(transferRole, address(0)) && from != address(0) && to != address(0)) {
if (!hasRole(transferRole, from) && !hasRole(transferRole, to)) {
revert("!T");
}
}
}

function _dropMsgSender() internal view virtual override returns (address) {
return _msgSender();
}

function _msgSenderERC721A() internal view virtual override returns (address) {
return _msgSender();
}

function _msgSender()
internal
view
virtual
override(ERC2771ContextUpgradeable, Multicall)
returns (address sender)
{
return ERC2771ContextUpgradeable._msgSender();
}
}
Loading

0 comments on commit effd208

Please sign in to comment.