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