@artblocks/contracts 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +27 -2
  2. package/artifacts/contracts/archive/GenArt721CoreV0.sol/Address.json +10 -0
  3. package/artifacts/contracts/archive/GenArt721CoreV0.sol/Counters.json +10 -0
  4. package/artifacts/contracts/archive/GenArt721CoreV0.sol/CustomERC721Metadata.json +425 -0
  5. package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC165.json +38 -0
  6. package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721.json +322 -0
  7. package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721Enumerable.json +384 -0
  8. package/artifacts/contracts/archive/GenArt721CoreV0.sol/ERC721ReceiverMock.json +101 -0
  9. package/artifacts/contracts/archive/GenArt721CoreV0.sol/GenArt721.json +1528 -0
  10. package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC165.json +32 -0
  11. package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721.json +316 -0
  12. package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721Enumerable.json +378 -0
  13. package/artifacts/contracts/archive/GenArt721CoreV0.sol/IERC721Receiver.json +47 -0
  14. package/artifacts/contracts/archive/GenArt721CoreV0.sol/SafeMath.json +10 -0
  15. package/artifacts/contracts/archive/GenArt721CoreV0.sol/Strings.json +10 -0
  16. package/artifacts/contracts/archive/GenArt721CoreV1.sol/Address.json +10 -0
  17. package/artifacts/contracts/archive/GenArt721CoreV1.sol/Counters.json +10 -0
  18. package/artifacts/contracts/archive/GenArt721CoreV1.sol/CustomERC721Metadata.json +425 -0
  19. package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC165.json +38 -0
  20. package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC721.json +322 -0
  21. package/artifacts/contracts/archive/GenArt721CoreV1.sol/ERC721Enumerable.json +384 -0
  22. package/artifacts/contracts/archive/GenArt721CoreV1.sol/GenArt721Core.json +1773 -0
  23. package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC165.json +32 -0
  24. package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721.json +316 -0
  25. package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721Enumerable.json +378 -0
  26. package/artifacts/contracts/archive/GenArt721CoreV1.sol/IERC721Receiver.json +47 -0
  27. package/artifacts/contracts/archive/GenArt721CoreV1.sol/Randomizer.json +26 -0
  28. package/artifacts/contracts/archive/GenArt721CoreV1.sol/SafeMath.json +10 -0
  29. package/artifacts/contracts/archive/GenArt721CoreV1.sol/Strings.json +10 -0
  30. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol/MinterDAExpV0.json +575 -0
  31. package/artifacts/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol/MinterDAExpV1.json +463 -0
  32. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV2.sol/MinterDAExpV2.json +3 -3
  33. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV3.sol/MinterDAExpV3.json +3 -3
  34. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol/MinterDAExpSettlementV0.json +15 -15
  35. package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol/MinterDALinV0.json +551 -0
  36. package/artifacts/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol/MinterDALinV1.json +439 -0
  37. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV2.sol/MinterDALinV2.json +3 -3
  38. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV3.sol/MinterDALinV3.json +3 -3
  39. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol/MinterHolderV0.json +622 -0
  40. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol/MinterHolderV1.json +719 -0
  41. package/artifacts/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +794 -0
  42. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV3.sol/MinterHolderV3.json +3 -3
  43. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol/MinterMerkleV0.json +873 -0
  44. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol/MinterMerkleV1.json +932 -0
  45. package/artifacts/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol/MinterMerkleV2.json +984 -0
  46. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV3.sol/MinterMerkleV3.json +3 -3
  47. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV4.sol/MinterMerkleV4.json +3 -3
  48. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol/MinterSetPriceV0.json +400 -0
  49. package/artifacts/contracts/{minter-suite/Minters/MinterSetPrice/deprecated → archive/minter-suite/Minters/MinterSetPrice}/MinterSetPriceV1.sol/MinterSetPriceV1.json +3 -3
  50. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV2.sol/MinterSetPriceV2.json +3 -3
  51. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV3.sol/MinterSetPriceV3.json +3 -3
  52. package/artifacts/contracts/{minter-suite/Minters/MinterSetPriceERC20/deprecated → archive/minter-suite/Minters/MinterSetPriceERC20}/MinterSetPriceERC20V0.sol/MinterSetPriceERC20V0.json +3 -3
  53. package/artifacts/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol/MinterSetPriceERC20V1.json +349 -0
  54. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol/MinterSetPriceERC20V2.json +3 -3
  55. package/artifacts/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol/MinterSetPriceERC20V3.json +3 -3
  56. package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol/IFilteredMinterDAExpSettlementV0.json +12 -12
  57. package/artifacts/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlement_Mixin.sol/IFilteredMinterDAExpSettlement_Mixin.json +161 -0
  58. package/artifacts/contracts/interfaces/0.8.x/IMinterBaseV0.sol/IMinterBaseV0.json +24 -0
  59. package/artifacts/contracts/interfaces/0.8.x/IMintingAPICoreV0.sol/IMintingAPICoreV0.json +185 -0
  60. package/artifacts/contracts/interfaces/0.8.x/IMintingAPIMinterV0.sol/IMintingAPIMinterV0.json +88 -0
  61. package/artifacts/contracts/minter-suite/Minters/MinterBase_v0_1_1.sol/MinterBase.json +24 -0
  62. package/artifacts/contracts/minter-suite/Minters/MinterDAExpSettlementV1.sol/MinterDAExpSettlementV1.json +1066 -0
  63. package/artifacts/contracts/minter-suite/Minters/MinterDAExpV4.sol/MinterDAExpV4.json +869 -0
  64. package/artifacts/contracts/minter-suite/Minters/MinterDALinV4.sol/MinterDALinV4.json +845 -0
  65. package/artifacts/contracts/minter-suite/Minters/MinterHolderV4.sol/MinterHolderV4.json +1113 -0
  66. package/artifacts/contracts/minter-suite/Minters/MinterMerkleV5.sol/MinterMerkleV5.json +1052 -0
  67. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol/MinterSetPriceERC20V4.json +755 -0
  68. package/artifacts/contracts/minter-suite/Minters/MinterSetPriceV4.sol/MinterSetPriceV4.json +684 -0
  69. package/artifacts/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol/GenArt721CoreV3_Engine_IncorrectCoreType.json +1817 -0
  70. package/contracts/archive/GenArt721CoreV0.sol +1621 -0
  71. package/contracts/archive/GenArt721CoreV1.sol +1577 -0
  72. package/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV0.sol +503 -0
  73. package/contracts/archive/minter-suite/Minters/MinterDAExp/MinterDAExpV1.sol +442 -0
  74. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV2.sol +3 -3
  75. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExp/MinterDAExpV3.sol +3 -3
  76. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDAExpSettlement/MinterDAExpSettlementV0.sol +3 -3
  77. package/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV0.sol +450 -0
  78. package/contracts/archive/minter-suite/Minters/MinterDALin/MinterDALinV1.sol +406 -0
  79. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV2.sol +3 -3
  80. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterDALin/MinterDALinV3.sol +3 -3
  81. package/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV0.sol +540 -0
  82. package/contracts/archive/minter-suite/Minters/MinterHolder/MinterHolderV1.sol +648 -0
  83. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV2.sol +6 -6
  84. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterHolder/MinterHolderV3.sol +4 -4
  85. package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV0.sol +523 -0
  86. package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV1.sol +602 -0
  87. package/contracts/archive/minter-suite/Minters/MinterMerkle/MinterMerkleV2.sol +679 -0
  88. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV3.sol +4 -4
  89. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterMerkle/MinterMerkleV4.sol +4 -4
  90. package/contracts/archive/minter-suite/Minters/MinterSetPrice/MinterSetPriceV0.sol +333 -0
  91. package/contracts/{minter-suite/Minters/MinterSetPrice/deprecated → archive/minter-suite/Minters/MinterSetPrice}/MinterSetPriceV1.sol +0 -0
  92. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV2.sol +3 -3
  93. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPrice/MinterSetPriceV3.sol +3 -3
  94. package/contracts/{minter-suite/Minters/MinterSetPriceERC20/deprecated → archive/minter-suite/Minters/MinterSetPriceERC20}/MinterSetPriceERC20V0.sol +0 -0
  95. package/contracts/archive/minter-suite/Minters/MinterSetPriceERC20/MinterSetPriceERC20V1.sol +411 -0
  96. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V2.sol +3 -3
  97. package/contracts/{minter-suite → archive/minter-suite}/Minters/MinterSetPriceERC20/MinterSetPriceERC20V3.sol +3 -3
  98. package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlementV0.sol +4 -53
  99. package/contracts/interfaces/0.8.x/IFilteredMinterDAExpSettlement_Mixin.sol +62 -0
  100. package/contracts/interfaces/0.8.x/IMinterBaseV0.sol +20 -0
  101. package/contracts/interfaces/0.8.x/IMintingAPICoreV0.sol +62 -0
  102. package/contracts/interfaces/0.8.x/IMintingAPIMinterV0.sol +31 -0
  103. package/contracts/minter-suite/Minters/MinterBase_v0_1_1.sol +293 -0
  104. package/contracts/minter-suite/Minters/MinterDAExpSettlementV1.sol +1026 -0
  105. package/contracts/minter-suite/Minters/MinterDAExpV4.sol +572 -0
  106. package/contracts/minter-suite/Minters/MinterDALinV4.sol +525 -0
  107. package/contracts/minter-suite/Minters/MinterHolderV4.sol +752 -0
  108. package/contracts/minter-suite/Minters/MinterMerkleV5.sol +688 -0
  109. package/contracts/minter-suite/Minters/MinterSetPriceERC20V4.sol +451 -0
  110. package/contracts/minter-suite/Minters/MinterSetPriceV4.sol +352 -0
  111. package/contracts/mock/GenArt721CoreV3_Engine_IncorrectCoreType.sol +1979 -0
  112. package/package.json +1 -1
  113. package/artifacts/contracts/minter-suite/Minters/MinterHolder/MinterHolderV2.sol/MinterHolderV2.json +0 -794
@@ -0,0 +1,752 @@
1
+ // SPDX-License-Identifier: LGPL-3.0-only
2
+ // Created By: Art Blocks Inc.
3
+
4
+ import "../../interfaces/0.8.x/IGenArt721CoreContractV3_Base.sol";
5
+ import "../../interfaces/0.8.x/IMinterFilterV0.sol";
6
+ import "../../interfaces/0.8.x/IFilteredMinterHolderV2.sol";
7
+ import "./MinterBase_v0_1_1.sol";
8
+ import "../../interfaces/0.8.x/IDelegationRegistry.sol";
9
+
10
+ import "@openzeppelin-4.5/contracts/token/ERC721/IERC721.sol";
11
+ import "@openzeppelin-4.5/contracts/security/ReentrancyGuard.sol";
12
+ import "@openzeppelin-4.5/contracts/utils/structs/EnumerableSet.sol";
13
+
14
+ pragma solidity 0.8.17;
15
+
16
+ /**
17
+ * @title Filtered Minter contract that allows tokens to be minted with ETH
18
+ * when purchaser owns an allowlisted ERC-721 NFT. This contract does NOT track
19
+ * if a purchaser has/has not minted already -- it simply restricts purchasing
20
+ * to anybody that holds one or more of a specified list of ERC-721 NFTs.
21
+ * This is designed to be used with GenArt721CoreContractV3 flagship or
22
+ * engine contracts.
23
+ * @author Art Blocks Inc.
24
+ * @notice Privileged Roles and Ownership:
25
+ * This contract is designed to be managed, with limited powers.
26
+ * Privileged roles and abilities are controlled by the core contract's Admin
27
+ * ACL contract and a project's artist. Both of these roles hold extensive
28
+ * power and can modify minter details.
29
+ * Care must be taken to ensure that the admin ACL contract and artist
30
+ * addresses are secure behind a multi-sig or other access control mechanism.
31
+ * ----------------------------------------------------------------------------
32
+ * The following functions are restricted to the core contract's Admin ACL
33
+ * contract:
34
+ * - registerNFTAddress
35
+ * - unregisterNFTAddress
36
+ * ----------------------------------------------------------------------------
37
+ * The following functions are restricted to a project's artist:
38
+ * - allowHoldersOfProjects
39
+ * - removeHoldersOfProjects
40
+ * - allowRemoveHoldersOfProjects
41
+ * - updatePricePerTokenInWei
42
+ * - setProjectMaxInvocations
43
+ * - manuallyLimitProjectMaxInvocations
44
+ * ----------------------------------------------------------------------------
45
+ * Additional admin and artist privileged roles may be described on other
46
+ * contracts that this minter integrates with.
47
+ * ----------------------------------------------------------------------------
48
+ * This contract allows gated minting with support for vaults to delegate minting
49
+ * privileges via an external delegation registry. This means a vault holding an
50
+ * allowed token can delegate minting privileges to a wallet that is not holding an
51
+ * allowed token, enabling the vault to remain air-gapped while still allowing minting.
52
+ * The delegation registry contract is responsible for managing these delegations,
53
+ * and is available at the address returned by the public immutable
54
+ * `delegationRegistryAddress`. At the time of writing, the delegation
55
+ * registry enables easy delegation configuring at https://delegate.cash/.
56
+ * Art Blocks does not guarentee the security of the delegation registry, and
57
+ * users should take care to ensure that the delegation registry is secure.
58
+ * Delegations must be configured by the vault owner prior to purchase. Supported
59
+ * delegation types include token-level, contract-level (via genArt721CoreAddress), or
60
+ * wallet-level delegation. Contract-level delegations must be configured for the core
61
+ * token contract as returned by the public immutable variable `genArt721CoreAddress`.
62
+ */
63
+ contract MinterHolderV4 is
64
+ ReentrancyGuard,
65
+ MinterBase,
66
+ IFilteredMinterHolderV2
67
+ {
68
+ // add Enumerable Set methods
69
+ using EnumerableSet for EnumerableSet.AddressSet;
70
+
71
+ /// Delegation registry address
72
+ address public immutable delegationRegistryAddress;
73
+
74
+ /// Delegation registry address
75
+ IDelegationRegistry private immutable delegationRegistryContract;
76
+
77
+ /// Core contract address this minter interacts with
78
+ address public immutable genArt721CoreAddress;
79
+
80
+ /// The core contract integrates with V3 contracts
81
+ IGenArt721CoreContractV3_Base private immutable genArtCoreContract_Base;
82
+
83
+ /// Minter filter address this minter interacts with
84
+ address public immutable minterFilterAddress;
85
+
86
+ /// Minter filter this minter may interact with.
87
+ IMinterFilterV0 private immutable minterFilter;
88
+
89
+ /// minterType for this minter
90
+ string public constant minterType = "MinterHolderV4";
91
+
92
+ uint256 constant ONE_MILLION = 1_000_000;
93
+
94
+ struct ProjectConfig {
95
+ bool maxHasBeenInvoked;
96
+ bool priceIsConfigured;
97
+ uint24 maxInvocations;
98
+ uint256 pricePerTokenInWei;
99
+ }
100
+
101
+ mapping(uint256 => ProjectConfig) public projectConfig;
102
+
103
+ /// Set of core contracts allowed to be queried for token holders
104
+ EnumerableSet.AddressSet private _registeredNFTAddresses;
105
+
106
+ /**
107
+ * projectId => ownedNFTAddress => ownedNFTProjectIds => bool
108
+ * projects whose holders are allowed to purchase a token on `projectId`
109
+ */
110
+ mapping(uint256 => mapping(address => mapping(uint256 => bool)))
111
+ public allowedProjectHolders;
112
+
113
+ // modifier to restrict access to only AdminACL allowed calls
114
+ // @dev defers which ACL contract is used to the core contract
115
+ modifier onlyCoreAdminACL(bytes4 _selector) {
116
+ require(
117
+ genArtCoreContract_Base.adminACLAllowed(
118
+ msg.sender,
119
+ address(this),
120
+ _selector
121
+ ),
122
+ "Only Core AdminACL allowed"
123
+ );
124
+ _;
125
+ }
126
+
127
+ modifier onlyArtist(uint256 _projectId) {
128
+ require(
129
+ msg.sender ==
130
+ genArtCoreContract_Base.projectIdToArtistAddress(_projectId),
131
+ "Only Artist"
132
+ );
133
+ _;
134
+ }
135
+
136
+ /**
137
+ * @notice Initializes contract to be a Filtered Minter for
138
+ * `_minterFilter`, integrated with Art Blocks core contract
139
+ * at address `_genArt721Address`.
140
+ * @param _genArt721Address Art Blocks core contract for which this
141
+ * contract will be a minter.
142
+ * @param _minterFilter Minter filter for which
143
+ * this will a filtered minter.
144
+ * @param _delegationRegistryAddress Delegation registry contract address.
145
+ */
146
+ constructor(
147
+ address _genArt721Address,
148
+ address _minterFilter,
149
+ address _delegationRegistryAddress
150
+ ) ReentrancyGuard() MinterBase(_genArt721Address) {
151
+ genArt721CoreAddress = _genArt721Address;
152
+ // always populate immutable engine contracts, but only use appropriate
153
+ // interface based on isEngine in the rest of the contract
154
+ genArtCoreContract_Base = IGenArt721CoreContractV3_Base(
155
+ _genArt721Address
156
+ );
157
+ delegationRegistryAddress = _delegationRegistryAddress;
158
+ emit DelegationRegistryUpdated(_delegationRegistryAddress);
159
+ delegationRegistryContract = IDelegationRegistry(
160
+ _delegationRegistryAddress
161
+ );
162
+ minterFilterAddress = _minterFilter;
163
+ minterFilter = IMinterFilterV0(_minterFilter);
164
+ require(
165
+ minterFilter.genArt721CoreAddress() == _genArt721Address,
166
+ "Illegal contract pairing"
167
+ );
168
+ }
169
+
170
+ /**
171
+ *
172
+ * @notice Registers holders of NFTs at address `_NFTAddress` to be
173
+ * considered for minting. New core address is assumed to follow syntax of:
174
+ * `projectId = tokenId / 1_000_000`
175
+ * @param _NFTAddress NFT core address to be registered.
176
+ */
177
+ function registerNFTAddress(
178
+ address _NFTAddress
179
+ ) external onlyCoreAdminACL(this.registerNFTAddress.selector) {
180
+ _registeredNFTAddresses.add(_NFTAddress);
181
+ emit RegisteredNFTAddress(_NFTAddress);
182
+ }
183
+
184
+ /**
185
+ *
186
+ * @notice Unregisters holders of NFTs at address `_NFTAddress` to be
187
+ * considered for adding to future allowlists.
188
+ * @param _NFTAddress NFT core address to be unregistered.
189
+ */
190
+ function unregisterNFTAddress(
191
+ address _NFTAddress
192
+ ) external onlyCoreAdminACL(this.unregisterNFTAddress.selector) {
193
+ _registeredNFTAddresses.remove(_NFTAddress);
194
+ emit UnregisteredNFTAddress(_NFTAddress);
195
+ }
196
+
197
+ /**
198
+ * @notice Allows holders of NFTs at addresses `_ownedNFTAddresses`,
199
+ * project IDs `_ownedNFTProjectIds` to mint on project `_projectId`.
200
+ * `_ownedNFTAddresses` assumed to be aligned with `_ownedNFTProjectIds`.
201
+ * e.g. Allows holders of project `_ownedNFTProjectIds[0]` on token
202
+ * contract `_ownedNFTAddresses[0]` to mint `_projectId`.
203
+ * @param _projectId Project ID to enable minting on.
204
+ * @param _ownedNFTAddresses NFT core addresses of projects to be
205
+ * allowlisted. Indexes must align with `_ownedNFTProjectIds`.
206
+ * @param _ownedNFTProjectIds Project IDs on `_ownedNFTAddresses` whose
207
+ * holders shall be allowlisted to mint project `_projectId`. Indexes must
208
+ * align with `_ownedNFTAddresses`.
209
+ */
210
+ function allowHoldersOfProjects(
211
+ uint256 _projectId,
212
+ address[] memory _ownedNFTAddresses,
213
+ uint256[] memory _ownedNFTProjectIds
214
+ ) public onlyArtist(_projectId) {
215
+ // require same length arrays
216
+ require(
217
+ _ownedNFTAddresses.length == _ownedNFTProjectIds.length,
218
+ "Length of add arrays must match"
219
+ );
220
+ // for each approved project
221
+ for (uint256 i = 0; i < _ownedNFTAddresses.length; i++) {
222
+ // ensure registered address
223
+ require(
224
+ _registeredNFTAddresses.contains(_ownedNFTAddresses[i]),
225
+ "Only Registered NFT Addresses"
226
+ );
227
+ // approve
228
+ allowedProjectHolders[_projectId][_ownedNFTAddresses[i]][
229
+ _ownedNFTProjectIds[i]
230
+ ] = true;
231
+ }
232
+ // emit approve event
233
+ emit AllowedHoldersOfProjects(
234
+ _projectId,
235
+ _ownedNFTAddresses,
236
+ _ownedNFTProjectIds
237
+ );
238
+ }
239
+
240
+ /**
241
+ * @notice Removes holders of NFTs at addresses `_ownedNFTAddresses`,
242
+ * project IDs `_ownedNFTProjectIds` to mint on project `_projectId`. If
243
+ * other projects owned by a holder are still allowed to mint, holder will
244
+ * maintain ability to purchase.
245
+ * `_ownedNFTAddresses` assumed to be aligned with `_ownedNFTProjectIds`.
246
+ * e.g. Removes holders of project `_ownedNFTProjectIds[0]` on token
247
+ * contract `_ownedNFTAddresses[0]` from mint allowlist of `_projectId`.
248
+ * @param _projectId Project ID to enable minting on.
249
+ * @param _ownedNFTAddresses NFT core addresses of projects to be removed
250
+ * from allowlist. Indexes must align with `_ownedNFTProjectIds`.
251
+ * @param _ownedNFTProjectIds Project IDs on `_ownedNFTAddresses` whose
252
+ * holders will be removed from allowlist to mint project `_projectId`.
253
+ * Indexes must align with `_ownedNFTAddresses`.
254
+ */
255
+ function removeHoldersOfProjects(
256
+ uint256 _projectId,
257
+ address[] memory _ownedNFTAddresses,
258
+ uint256[] memory _ownedNFTProjectIds
259
+ ) public onlyArtist(_projectId) {
260
+ // require same length arrays
261
+ require(
262
+ _ownedNFTAddresses.length == _ownedNFTProjectIds.length,
263
+ "Length of remove arrays must match"
264
+ );
265
+ // for each removed project
266
+ for (uint256 i = 0; i < _ownedNFTAddresses.length; i++) {
267
+ // revoke
268
+ allowedProjectHolders[_projectId][_ownedNFTAddresses[i]][
269
+ _ownedNFTProjectIds[i]
270
+ ] = false;
271
+ }
272
+ // emit removed event
273
+ emit RemovedHoldersOfProjects(
274
+ _projectId,
275
+ _ownedNFTAddresses,
276
+ _ownedNFTProjectIds
277
+ );
278
+ }
279
+
280
+ /**
281
+ * @notice Allows holders of NFTs at addresses `_ownedNFTAddressesAdd`,
282
+ * project IDs `_ownedNFTProjectIdsAdd` to mint on project `_projectId`.
283
+ * Also removes holders of NFTs at addresses `_ownedNFTAddressesRemove`,
284
+ * project IDs `_ownedNFTProjectIdsRemove` from minting on project
285
+ * `_projectId`.
286
+ * `_ownedNFTAddressesAdd` assumed to be aligned with
287
+ * `_ownedNFTProjectIdsAdd`.
288
+ * e.g. Allows holders of project `_ownedNFTProjectIdsAdd[0]` on token
289
+ * contract `_ownedNFTAddressesAdd[0]` to mint `_projectId`.
290
+ * `_ownedNFTAddressesRemove` also assumed to be aligned with
291
+ * `_ownedNFTProjectIdsRemove`.
292
+ * @param _projectId Project ID to enable minting on.
293
+ * @param _ownedNFTAddressesAdd NFT core addresses of projects to be
294
+ * allowlisted. Indexes must align with `_ownedNFTProjectIdsAdd`.
295
+ * @param _ownedNFTProjectIdsAdd Project IDs on `_ownedNFTAddressesAdd`
296
+ * whose holders shall be allowlisted to mint project `_projectId`. Indexes
297
+ * must align with `_ownedNFTAddressesAdd`.
298
+ * @param _ownedNFTAddressesRemove NFT core addresses of projects to be
299
+ * removed from allowlist. Indexes must align with
300
+ * `_ownedNFTProjectIdsRemove`.
301
+ * @param _ownedNFTProjectIdsRemove Project IDs on
302
+ * `_ownedNFTAddressesRemove` whose holders will be removed from allowlist
303
+ * to mint project `_projectId`. Indexes must align with
304
+ * `_ownedNFTAddressesRemove`.
305
+ * @dev if a project is included in both add and remove arrays, it will be
306
+ * removed.
307
+ */
308
+ function allowRemoveHoldersOfProjects(
309
+ uint256 _projectId,
310
+ address[] memory _ownedNFTAddressesAdd,
311
+ uint256[] memory _ownedNFTProjectIdsAdd,
312
+ address[] memory _ownedNFTAddressesRemove,
313
+ uint256[] memory _ownedNFTProjectIdsRemove
314
+ ) external onlyArtist(_projectId) {
315
+ allowHoldersOfProjects(
316
+ _projectId,
317
+ _ownedNFTAddressesAdd,
318
+ _ownedNFTProjectIdsAdd
319
+ );
320
+ removeHoldersOfProjects(
321
+ _projectId,
322
+ _ownedNFTAddressesRemove,
323
+ _ownedNFTProjectIdsRemove
324
+ );
325
+ }
326
+
327
+ /**
328
+ * @notice Returns if token is an allowlisted NFT for project `_projectId`.
329
+ * @param _projectId Project ID to be checked.
330
+ * @param _ownedNFTAddress ERC-721 NFT token address to be checked.
331
+ * @param _ownedNFTTokenId ERC-721 NFT token ID to be checked.
332
+ * @return bool Token is allowlisted
333
+ * @dev does not check if token has been used to purchase
334
+ * @dev assumes project ID can be derived from tokenId / 1_000_000
335
+ */
336
+ function isAllowlistedNFT(
337
+ uint256 _projectId,
338
+ address _ownedNFTAddress,
339
+ uint256 _ownedNFTTokenId
340
+ ) public view returns (bool) {
341
+ uint256 ownedNFTProjectId = _ownedNFTTokenId / ONE_MILLION;
342
+ return
343
+ allowedProjectHolders[_projectId][_ownedNFTAddress][
344
+ ownedNFTProjectId
345
+ ];
346
+ }
347
+
348
+ /**
349
+ * @notice Syncs local maximum invocations of project `_projectId` based on
350
+ * the value currently defined in the core contract.
351
+ * @param _projectId Project ID to set the maximum invocations for.
352
+ * @dev this enables gas reduction after maxInvocations have been reached -
353
+ * core contracts shall still enforce a maxInvocation check during mint.
354
+ */
355
+ function setProjectMaxInvocations(uint256 _projectId) external {
356
+ uint256 maxInvocations;
357
+ uint256 invocations;
358
+ (invocations, maxInvocations, , , , ) = genArtCoreContract_Base
359
+ .projectStateData(_projectId);
360
+ // update storage with results
361
+ projectConfig[_projectId].maxInvocations = uint24(maxInvocations);
362
+
363
+ // We need to ensure maxHasBeenInvoked is correctly set after manually syncing the
364
+ // local maxInvocations value with the core contract's maxInvocations value.
365
+ // This synced value of maxInvocations from the core contract will always be greater
366
+ // than or equal to the previous value of maxInvocations stored locally.
367
+ projectConfig[_projectId].maxHasBeenInvoked =
368
+ invocations == maxInvocations;
369
+
370
+ emit ProjectMaxInvocationsLimitUpdated(_projectId, maxInvocations);
371
+ }
372
+
373
+ /**
374
+ * @notice Manually sets the local maximum invocations of project `_projectId`
375
+ * with the provided `_maxInvocations`, checking that `_maxInvocations` is less
376
+ * than or equal to the value of project `_project_id`'s maximum invocations that is
377
+ * set on the core contract.
378
+ * @dev Note that a `_maxInvocations` of 0 can only be set if the current `invocations`
379
+ * value is also 0 and this would also set `maxHasBeenInvoked` to true, correctly short-circuiting
380
+ * this minter's purchase function, avoiding extra gas costs from the core contract's maxInvocations check.
381
+ * @param _projectId Project ID to set the maximum invocations for.
382
+ * @param _maxInvocations Maximum invocations to set for the project.
383
+ */
384
+ function manuallyLimitProjectMaxInvocations(
385
+ uint256 _projectId,
386
+ uint256 _maxInvocations
387
+ ) external onlyArtist(_projectId) {
388
+ // CHECKS
389
+ // ensure that the manually set maxInvocations is not greater than what is set on the core contract
390
+ uint256 maxInvocations;
391
+ uint256 invocations;
392
+ (invocations, maxInvocations, , , , ) = genArtCoreContract_Base
393
+ .projectStateData(_projectId);
394
+ require(
395
+ _maxInvocations <= maxInvocations,
396
+ "Cannot increase project max invocations above core contract set project max invocations"
397
+ );
398
+ require(
399
+ _maxInvocations >= invocations,
400
+ "Cannot set project max invocations to less than current invocations"
401
+ );
402
+
403
+ // EFFECTS
404
+ // update storage with results
405
+ projectConfig[_projectId].maxInvocations = uint24(_maxInvocations);
406
+ // We need to ensure maxHasBeenInvoked is correctly set after manually setting the
407
+ // local maxInvocations value.
408
+ projectConfig[_projectId].maxHasBeenInvoked =
409
+ invocations == _maxInvocations;
410
+
411
+ emit ProjectMaxInvocationsLimitUpdated(_projectId, _maxInvocations);
412
+ }
413
+
414
+ /**
415
+ * @notice Warning: Disabling purchaseTo is not supported on this minter.
416
+ * This method exists purely for interface-conformance purposes.
417
+ */
418
+ function togglePurchaseToDisabled(
419
+ uint256 _projectId
420
+ ) external view onlyArtist(_projectId) {
421
+ revert("Action not supported");
422
+ }
423
+
424
+ /**
425
+ * @notice projectId => has project reached its maximum number of
426
+ * invocations? Note that this returns a local cache of the core contract's
427
+ * state, and may be out of sync with the core contract. This is
428
+ * intentional, as it only enables gas optimization of mints after a
429
+ * project's maximum invocations has been reached. A false negative will
430
+ * only result in a gas cost increase, since the core contract will still
431
+ * enforce a maxInvocation check during minting. A false positive is not
432
+ * possible because the V3 core contract only allows maximum invocations
433
+ * to be reduced, not increased. Based on this rationale, we intentionally
434
+ * do not do input validation in this method as to whether or not the input
435
+ * `_projectId` is an existing project ID.
436
+ */
437
+ function projectMaxHasBeenInvoked(
438
+ uint256 _projectId
439
+ ) external view returns (bool) {
440
+ return projectConfig[_projectId].maxHasBeenInvoked;
441
+ }
442
+
443
+ /**
444
+ * @notice projectId => project's maximum number of invocations.
445
+ * Optionally synced with core contract value, for gas optimization.
446
+ * Note that this returns a local cache of the core contract's
447
+ * state, and may be out of sync with the core contract. This is
448
+ * intentional, as it only enables gas optimization of mints after a
449
+ * project's maximum invocations has been reached.
450
+ * @dev A number greater than the core contract's project max invocations
451
+ * will only result in a gas cost increase, since the core contract will
452
+ * still enforce a maxInvocation check during minting. A number less than
453
+ * the core contract's project max invocations is only possible when the
454
+ * project's max invocations have not been synced on this minter, since the
455
+ * V3 core contract only allows maximum invocations to be reduced, not
456
+ * increased. When this happens, the minter will enable minting, allowing
457
+ * the core contract to enforce the max invocations check. Based on this
458
+ * rationale, we intentionally do not do input validation in this method as
459
+ * to whether or not the input `_projectId` is an existing project ID.
460
+ */
461
+ function projectMaxInvocations(
462
+ uint256 _projectId
463
+ ) external view returns (uint256) {
464
+ return uint256(projectConfig[_projectId].maxInvocations);
465
+ }
466
+
467
+ /**
468
+ * @notice Updates this minter's price per token of project `_projectId`
469
+ * to be '_pricePerTokenInWei`, in Wei.
470
+ * This price supersedes any legacy core contract price per token value.
471
+ * @dev Note that it is intentionally supported here that the configured
472
+ * price may be explicitly set to `0`.
473
+ */
474
+ function updatePricePerTokenInWei(
475
+ uint256 _projectId,
476
+ uint256 _pricePerTokenInWei
477
+ ) external onlyArtist(_projectId) {
478
+ projectConfig[_projectId].pricePerTokenInWei = _pricePerTokenInWei;
479
+ projectConfig[_projectId].priceIsConfigured = true;
480
+ emit PricePerTokenInWeiUpdated(_projectId, _pricePerTokenInWei);
481
+ }
482
+
483
+ /**
484
+ * @notice Inactive function - requires NFT ownership to purchase.
485
+ */
486
+ function purchase(uint256) external payable returns (uint256) {
487
+ revert("Must claim NFT ownership");
488
+ }
489
+
490
+ /**
491
+ * @notice Inactive function - requires NFT ownership to purchase.
492
+ */
493
+ function purchaseTo(address, uint256) public payable returns (uint256) {
494
+ revert("Must claim NFT ownership");
495
+ }
496
+
497
+ /**
498
+ * @notice Purchases a token from project `_projectId`.
499
+ * @param _projectId Project ID to mint a token on.
500
+ * @param _ownedNFTAddress ERC-721 NFT address holding the project token
501
+ * owned by msg.sender being used to prove right to purchase.
502
+ * @param _ownedNFTTokenId ERC-721 NFT token ID owned by msg.sender being used
503
+ * to prove right to purchase.
504
+ * @return tokenId Token ID of minted token
505
+ */
506
+ function purchase(
507
+ uint256 _projectId,
508
+ address _ownedNFTAddress,
509
+ uint256 _ownedNFTTokenId
510
+ ) external payable returns (uint256 tokenId) {
511
+ tokenId = purchaseTo_dlc(
512
+ msg.sender,
513
+ _projectId,
514
+ _ownedNFTAddress,
515
+ _ownedNFTTokenId,
516
+ address(0)
517
+ );
518
+ return tokenId;
519
+ }
520
+
521
+ /**
522
+ * @notice gas-optimized version of purchase(uint256,address,uint256).
523
+ */
524
+ function purchase_nnf(
525
+ uint256 _projectId,
526
+ address _ownedNFTAddress,
527
+ uint256 _ownedNFTTokenId
528
+ ) external payable returns (uint256 tokenId) {
529
+ tokenId = purchaseTo_dlc(
530
+ msg.sender,
531
+ _projectId,
532
+ _ownedNFTAddress,
533
+ _ownedNFTTokenId,
534
+ address(0)
535
+ );
536
+ return tokenId;
537
+ }
538
+
539
+ /**
540
+ * @notice Purchases a token from project `_projectId` and sets
541
+ * the token's owner to `_to`.
542
+ * @param _to Address to be the new token's owner.
543
+ * @param _projectId Project ID to mint a token on.
544
+ * @param _ownedNFTAddress ERC-721 NFT holding the project token owned by
545
+ * msg.sender being used to claim right to purchase.
546
+ * @param _ownedNFTTokenId ERC-721 NFT token ID owned by msg.sender being used
547
+ * to claim right to purchase.
548
+ * @return tokenId Token ID of minted token
549
+ */
550
+ function purchaseTo(
551
+ address _to,
552
+ uint256 _projectId,
553
+ address _ownedNFTAddress,
554
+ uint256 _ownedNFTTokenId
555
+ ) external payable returns (uint256 tokenId) {
556
+ return
557
+ purchaseTo_dlc(
558
+ _to,
559
+ _projectId,
560
+ _ownedNFTAddress,
561
+ _ownedNFTTokenId,
562
+ address(0)
563
+ );
564
+ }
565
+
566
+ /**
567
+ * @notice Purchases a token from project `_projectId` and sets
568
+ * the token's owner to `_to`, as a delegate, (the `msg.sender`)
569
+ * on behalf of an explicitly defined vault.
570
+ * @param _to Address to be the new token's owner.
571
+ * @param _projectId Project ID to mint a token on.
572
+ * @param _ownedNFTAddress ERC-721 NFT address holding the project token owned by
573
+ * _vault (or msg.sender if no _vault is provided) being used to claim right to purchase.
574
+ * @param _ownedNFTTokenId ERC-721 NFT token ID owned by _vault (or msg.sender if
575
+ * no _vault is provided) being used to claim right to purchase.
576
+ * @param _vault Vault being purchased on behalf of. Acceptable to be `address(0)` if no vault.
577
+ * @return tokenId Token ID of minted token
578
+ */
579
+ function purchaseTo(
580
+ address _to,
581
+ uint256 _projectId,
582
+ address _ownedNFTAddress,
583
+ uint256 _ownedNFTTokenId,
584
+ address _vault
585
+ ) external payable returns (uint256 tokenId) {
586
+ return
587
+ purchaseTo_dlc(
588
+ _to,
589
+ _projectId,
590
+ _ownedNFTAddress,
591
+ _ownedNFTTokenId,
592
+ _vault
593
+ );
594
+ }
595
+
596
+ /**
597
+ * @notice gas-optimized version of purchaseTo(address,uint256,address,uint256,address).
598
+ * @param _to Address to be the new token's owner.
599
+ * @param _projectId Project ID to mint a token on.
600
+ * @param _ownedNFTAddress ERC-721 NFT address holding the project token owned by _vault
601
+ * (or msg.sender if no _vault is provided) being used to claim right to purchase.
602
+ * @param _ownedNFTTokenId ERC-721 NFT token ID owned by _vault (or msg.sender if
603
+ * no _vault is provided) being used to claim right to purchase.
604
+ * @param _vault Vault being purchased on behalf of. Acceptable to be `address(0)` if no vault.
605
+ * @return tokenId Token ID of minted token
606
+ */
607
+ function purchaseTo_dlc(
608
+ address _to,
609
+ uint256 _projectId,
610
+ address _ownedNFTAddress,
611
+ uint256 _ownedNFTTokenId,
612
+ address _vault
613
+ ) public payable nonReentrant returns (uint256 tokenId) {
614
+ // CHECKS
615
+ ProjectConfig storage _projectConfig = projectConfig[_projectId];
616
+
617
+ // Note that `maxHasBeenInvoked` is only checked here to reduce gas
618
+ // consumption after a project has been fully minted.
619
+ // `_projectConfig.maxHasBeenInvoked` is locally cached to reduce
620
+ // gas consumption, but if not in sync with the core contract's value,
621
+ // the core contract also enforces its own max invocation check during
622
+ // minting.
623
+ require(
624
+ !_projectConfig.maxHasBeenInvoked,
625
+ "Maximum number of invocations reached"
626
+ );
627
+
628
+ // load price of token into memory
629
+ uint256 pricePerTokenInWei = _projectConfig.pricePerTokenInWei;
630
+
631
+ require(
632
+ msg.value >= pricePerTokenInWei,
633
+ "Must send minimum value to mint!"
634
+ );
635
+
636
+ // require artist to have configured price of token on this minter
637
+ require(_projectConfig.priceIsConfigured, "Price not configured");
638
+
639
+ // require token used to claim to be in set of allowlisted NFTs
640
+ require(
641
+ isAllowlistedNFT(_projectId, _ownedNFTAddress, _ownedNFTTokenId),
642
+ "Only allowlisted NFTs"
643
+ );
644
+
645
+ // NOTE: delegate-vault handling **begins here**.
646
+
647
+ // handle that the vault may be either the `msg.sender` in the case
648
+ // that there is not a true vault, or may be `_vault` if one is
649
+ // provided explicitly (and it is valid).
650
+ address vault = msg.sender;
651
+ if (_vault != address(0)) {
652
+ // If a vault is provided, it must be valid, otherwise throw rather
653
+ // than optimistically-minting with original `msg.sender`.
654
+ // Note, we do not check `checkDelegateForAll` or `checkDelegateForContract` as well,
655
+ // as they are known to be implicitly checked by calling `checkDelegateForToken`.
656
+ bool isValidVault = delegationRegistryContract
657
+ .checkDelegateForToken(
658
+ msg.sender, // delegate
659
+ _vault, // vault
660
+ genArt721CoreAddress, // contract
661
+ _ownedNFTTokenId // tokenId
662
+ );
663
+ require(isValidVault, "Invalid delegate-vault pairing");
664
+ vault = _vault;
665
+ }
666
+
667
+ // EFFECTS
668
+ tokenId = minterFilter.mint(_to, _projectId, vault);
669
+
670
+ // NOTE: delegate-vault handling **ends here**.
671
+
672
+ // okay if this underflows because if statement will always eval false.
673
+ // this is only for gas optimization (core enforces maxInvocations).
674
+ unchecked {
675
+ if (tokenId % ONE_MILLION == _projectConfig.maxInvocations - 1) {
676
+ _projectConfig.maxHasBeenInvoked = true;
677
+ }
678
+ }
679
+
680
+ // INTERACTIONS
681
+ // require sender to own NFT used to redeem
682
+ /**
683
+ * @dev Considered an interaction because calling ownerOf on an NFT
684
+ * contract. Plan is to only register AB/PBAB NFTs on the minter, but
685
+ * in case other NFTs are registered, better to check here. Also,
686
+ * function is non-reentrant, so being extra cautious.
687
+ */
688
+ require(
689
+ IERC721(_ownedNFTAddress).ownerOf(_ownedNFTTokenId) == vault,
690
+ "Only owner of NFT"
691
+ );
692
+
693
+ // split funds
694
+ splitFundsETH(_projectId, pricePerTokenInWei, genArt721CoreAddress);
695
+
696
+ return tokenId;
697
+ }
698
+
699
+ /**
700
+ * @notice Gets quantity of NFT addresses registered on this minter.
701
+ * @return uint256 quantity of NFT addresses registered
702
+ */
703
+ function getNumRegisteredNFTAddresses() external view returns (uint256) {
704
+ return _registeredNFTAddresses.length();
705
+ }
706
+
707
+ /**
708
+ * @notice Get registered NFT core contract address at index `_index` of
709
+ * enumerable set.
710
+ * @param _index enumerable set index to query.
711
+ * @return NFTAddress NFT core contract address at index `_index`
712
+ * @dev index must be < quantity of registered NFT addresses
713
+ */
714
+ function getRegisteredNFTAddressAt(
715
+ uint256 _index
716
+ ) external view returns (address NFTAddress) {
717
+ return _registeredNFTAddresses.at(_index);
718
+ }
719
+
720
+ /**
721
+ * @notice Gets if price of token is configured, price of minting a
722
+ * token on project `_projectId`, and currency symbol and address to be
723
+ * used as payment. Supersedes any core contract price information.
724
+ * @param _projectId Project ID to get price information for.
725
+ * @return isConfigured true only if token price has been configured on
726
+ * this minter
727
+ * @return tokenPriceInWei current price of token on this minter - invalid
728
+ * if price has not yet been configured
729
+ * @return currencySymbol currency symbol for purchases of project on this
730
+ * minter. This minter always returns "ETH"
731
+ * @return currencyAddress currency address for purchases of project on
732
+ * this minter. This minter always returns null address, reserved for ether
733
+ */
734
+ function getPriceInfo(
735
+ uint256 _projectId
736
+ )
737
+ external
738
+ view
739
+ returns (
740
+ bool isConfigured,
741
+ uint256 tokenPriceInWei,
742
+ string memory currencySymbol,
743
+ address currencyAddress
744
+ )
745
+ {
746
+ ProjectConfig storage _projectConfig = projectConfig[_projectId];
747
+ isConfigured = _projectConfig.priceIsConfigured;
748
+ tokenPriceInWei = _projectConfig.pricePerTokenInWei;
749
+ currencySymbol = "ETH";
750
+ currencyAddress = address(0);
751
+ }
752
+ }