@artblocks/contracts 0.0.1

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