Skip to content

Commit effd208

Browse files
authoredJan 26, 2024
OpenEditionERC721 flat platform fee (#614)
* OpenEditionERC721 with flat fee * flat fee support OE * fix tests
1 parent da53846 commit effd208

File tree

14 files changed

+2242
-0
lines changed

14 files changed

+2242
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
/// @author thirdweb
5+
6+
// $$\ $$\ $$\ $$\ $$\
7+
// $$ | $$ | \__| $$ | $$ |
8+
// $$$$$$\ $$$$$$$\ $$\ $$$$$$\ $$$$$$$ |$$\ $$\ $$\ $$$$$$\ $$$$$$$\
9+
// \_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$ |$$ | $$ | $$ |$$ __$$\ $$ __$$\
10+
// $$ | $$ | $$ |$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$$$$$$$ |$$ | $$ |
11+
// $$ |$$\ $$ | $$ |$$ |$$ | $$ | $$ |$$ | $$ | $$ |$$ ____|$$ | $$ |
12+
// \$$$$ |$$ | $$ |$$ |$$ | \$$$$$$$ |\$$$$$\$$$$ |\$$$$$$$\ $$$$$$$ |
13+
// \____/ \__| \__|\__|\__| \_______| \_____\____/ \_______|\_______/
14+
15+
// ========== External imports ==========
16+
17+
import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol";
18+
import "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol";
19+
20+
import "../../eip/queryable/ERC721AQueryableUpgradeable.sol";
21+
22+
// ========== Internal imports ==========
23+
24+
import "../../external-deps/openzeppelin/metatx/ERC2771ContextUpgradeable.sol";
25+
import "../../lib/CurrencyTransferLib.sol";
26+
27+
// ========== Features ==========
28+
29+
import "../../extension/Multicall.sol";
30+
import "../../extension/ContractMetadata.sol";
31+
import "../../extension/Royalty.sol";
32+
import "../../extension/PrimarySale.sol";
33+
import "../../extension/Ownable.sol";
34+
import "../../extension/SharedMetadata.sol";
35+
import "../../extension/PermissionsEnumerable.sol";
36+
import "../../extension/Drop.sol";
37+
import "../../extension/PlatformFee.sol";
38+
39+
contract OpenEditionERC721FlatFee is
40+
Initializable,
41+
ContractMetadata,
42+
PlatformFee,
43+
Royalty,
44+
PrimarySale,
45+
Ownable,
46+
SharedMetadata,
47+
PermissionsEnumerable,
48+
Drop,
49+
ERC2771ContextUpgradeable,
50+
Multicall,
51+
ERC721AQueryableUpgradeable
52+
{
53+
using StringsUpgradeable for uint256;
54+
55+
/*///////////////////////////////////////////////////////////////
56+
State variables
57+
//////////////////////////////////////////////////////////////*/
58+
59+
/// @dev Only transfers to or from TRANSFER_ROLE holders are valid, when transfers are restricted.
60+
bytes32 private transferRole;
61+
/// @dev Only MINTER_ROLE holders can update the shared metadata of tokens.
62+
bytes32 private minterRole;
63+
64+
/// @dev Max bps in the thirdweb system.
65+
uint256 private constant MAX_BPS = 10_000;
66+
67+
/*///////////////////////////////////////////////////////////////
68+
Constructor + initializer logic
69+
//////////////////////////////////////////////////////////////*/
70+
71+
constructor() initializer {}
72+
73+
/// @dev Initializes the contract, like a constructor.
74+
function initialize(
75+
address _defaultAdmin,
76+
string memory _name,
77+
string memory _symbol,
78+
string memory _contractURI,
79+
address[] memory _trustedForwarders,
80+
address _saleRecipient,
81+
address _royaltyRecipient,
82+
uint128 _royaltyBps,
83+
uint128 _platformFeeBps,
84+
address _platformFeeRecipient
85+
) external initializerERC721A initializer {
86+
bytes32 _transferRole = keccak256("TRANSFER_ROLE");
87+
bytes32 _minterRole = keccak256("MINTER_ROLE");
88+
89+
// Initialize inherited contracts, most base-like -> most derived.
90+
__ERC2771Context_init(_trustedForwarders);
91+
__ERC721A_init(_name, _symbol);
92+
93+
_setupContractURI(_contractURI);
94+
_setupOwner(_defaultAdmin);
95+
96+
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
97+
_setupRole(_minterRole, _defaultAdmin);
98+
_setupRole(_transferRole, _defaultAdmin);
99+
_setupRole(_transferRole, address(0));
100+
101+
_setupPlatformFeeInfo(_platformFeeRecipient, _platformFeeBps);
102+
_setupDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps);
103+
_setupPrimarySaleRecipient(_saleRecipient);
104+
105+
transferRole = _transferRole;
106+
minterRole = _minterRole;
107+
}
108+
109+
/*///////////////////////////////////////////////////////////////
110+
ERC 165 / 721 / 2981 logic
111+
//////////////////////////////////////////////////////////////*/
112+
113+
/// @dev Returns the URI for a given tokenId.
114+
function tokenURI(
115+
uint256 _tokenId
116+
) public view virtual override(ERC721AUpgradeable, IERC721AUpgradeable) returns (string memory) {
117+
if (!_exists(_tokenId)) {
118+
revert("!ID");
119+
}
120+
121+
return _getURIFromSharedMetadata(_tokenId);
122+
}
123+
124+
/// @dev See ERC 165
125+
function supportsInterface(
126+
bytes4 interfaceId
127+
) public view virtual override(ERC721AUpgradeable, IERC165, IERC721AUpgradeable) returns (bool) {
128+
return super.supportsInterface(interfaceId) || type(IERC2981Upgradeable).interfaceId == interfaceId;
129+
}
130+
131+
/// @dev The start token ID for the contract.
132+
function _startTokenId() internal pure override returns (uint256) {
133+
return 1;
134+
}
135+
136+
function startTokenId() public pure returns (uint256) {
137+
return _startTokenId();
138+
}
139+
140+
/*///////////////////////////////////////////////////////////////
141+
Internal functions
142+
//////////////////////////////////////////////////////////////*/
143+
144+
/// @dev Collects and distributes the primary sale value of NFTs being claimed.
145+
function _collectPriceOnClaim(
146+
address _primarySaleRecipient,
147+
uint256 _quantityToClaim,
148+
address _currency,
149+
uint256 _pricePerToken
150+
) internal override {
151+
if (_pricePerToken == 0) {
152+
require(msg.value == 0, "!Value");
153+
return;
154+
}
155+
156+
uint256 totalPrice = _quantityToClaim * _pricePerToken;
157+
uint256 platformFees;
158+
address platformFeeRecipient;
159+
160+
if (getPlatformFeeType() == IPlatformFee.PlatformFeeType.Flat) {
161+
(platformFeeRecipient, platformFees) = getFlatPlatformFeeInfo();
162+
} else {
163+
(address recipient, uint16 platformFeeBps) = getPlatformFeeInfo();
164+
platformFeeRecipient = recipient;
165+
platformFees = ((totalPrice * platformFeeBps) / MAX_BPS);
166+
}
167+
require(totalPrice >= platformFees, "price less than platform fee");
168+
169+
bool validMsgValue;
170+
if (_currency == CurrencyTransferLib.NATIVE_TOKEN) {
171+
validMsgValue = msg.value == totalPrice;
172+
} else {
173+
validMsgValue = msg.value == 0;
174+
}
175+
require(validMsgValue, "!V");
176+
177+
address saleRecipient = _primarySaleRecipient == address(0) ? primarySaleRecipient() : _primarySaleRecipient;
178+
179+
CurrencyTransferLib.transferCurrency(_currency, _msgSender(), platformFeeRecipient, platformFees);
180+
CurrencyTransferLib.transferCurrency(_currency, _msgSender(), saleRecipient, totalPrice - platformFees);
181+
}
182+
183+
/// @dev Transfers the NFTs being claimed.
184+
function _transferTokensOnClaim(
185+
address _to,
186+
uint256 _quantityBeingClaimed
187+
) internal override returns (uint256 startTokenId_) {
188+
startTokenId_ = _nextTokenId();
189+
_safeMint(_to, _quantityBeingClaimed);
190+
}
191+
192+
/// @dev Checks whether primary sale recipient can be set in the given execution context.
193+
function _canSetPrimarySaleRecipient() internal view override returns (bool) {
194+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
195+
}
196+
197+
/// @dev Checks whether owner can be set in the given execution context.
198+
function _canSetOwner() internal view override returns (bool) {
199+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
200+
}
201+
202+
/// @dev Checks whether royalty info can be set in the given execution context.
203+
function _canSetRoyaltyInfo() internal view override returns (bool) {
204+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
205+
}
206+
207+
/// @dev Checks whether contract metadata can be set in the given execution context.
208+
function _canSetContractURI() internal view override returns (bool) {
209+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
210+
}
211+
212+
/// @dev Checks whether platform fee info can be set in the given execution context.
213+
function _canSetClaimConditions() internal view override returns (bool) {
214+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
215+
}
216+
217+
/// @dev Returns whether the shared metadata of tokens can be set in the given execution context.
218+
function _canSetSharedMetadata() internal view virtual override returns (bool) {
219+
return hasRole(minterRole, _msgSender());
220+
}
221+
222+
/// @dev Checks whether platform fee info can be set in the given execution context.
223+
function _canSetPlatformFeeInfo() internal view override returns (bool) {
224+
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
225+
}
226+
227+
/*///////////////////////////////////////////////////////////////
228+
Miscellaneous
229+
//////////////////////////////////////////////////////////////*/
230+
231+
/**
232+
* Returns the total amount of tokens minted in the contract.
233+
*/
234+
function totalMinted() external view returns (uint256) {
235+
unchecked {
236+
return _nextTokenId() - _startTokenId();
237+
}
238+
}
239+
240+
/// @dev The tokenId of the next NFT that will be minted / lazy minted.
241+
function nextTokenIdToMint() external view returns (uint256) {
242+
return _nextTokenId();
243+
}
244+
245+
/// @dev The next token ID of the NFT that can be claimed.
246+
function nextTokenIdToClaim() external view returns (uint256) {
247+
return _nextTokenId();
248+
}
249+
250+
/// @dev Burns `tokenId`. See {ERC721-_burn}.
251+
function burn(uint256 tokenId) external virtual {
252+
// note: ERC721AUpgradeable's `_burn(uint256,bool)` internally checks for token approvals.
253+
_burn(tokenId, true);
254+
}
255+
256+
/// @dev See {ERC721-_beforeTokenTransfer}.
257+
function _beforeTokenTransfers(
258+
address from,
259+
address to,
260+
uint256 startTokenId_,
261+
uint256 quantity
262+
) internal virtual override {
263+
super._beforeTokenTransfers(from, to, startTokenId_, quantity);
264+
265+
// if transfer is restricted on the contract, we still want to allow burning and minting
266+
if (!hasRole(transferRole, address(0)) && from != address(0) && to != address(0)) {
267+
if (!hasRole(transferRole, from) && !hasRole(transferRole, to)) {
268+
revert("!T");
269+
}
270+
}
271+
}
272+
273+
function _dropMsgSender() internal view virtual override returns (address) {
274+
return _msgSender();
275+
}
276+
277+
function _msgSenderERC721A() internal view virtual override returns (address) {
278+
return _msgSender();
279+
}
280+
281+
function _msgSender()
282+
internal
283+
view
284+
virtual
285+
override(ERC2771ContextUpgradeable, Multicall)
286+
returns (address sender)
287+
{
288+
return ERC2771ContextUpgradeable._msgSender();
289+
}
290+
}

0 commit comments

Comments
 (0)
Please sign in to comment.