@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,717 @@
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/IFilteredMinterMerkleV2.sol";
7
+ import "../../interfaces/0.8.x/IDelegationRegistry.sol";
8
+ import "./MinterBase_v0_1_1.sol";
9
+
10
+ import "@openzeppelin-4.7/contracts/utils/cryptography/MerkleProof.sol";
11
+ import "@openzeppelin-4.7/contracts/token/ERC20/IERC20.sol";
12
+ import "@openzeppelin-4.7/contracts/security/ReentrancyGuard.sol";
13
+
14
+ pragma solidity 0.8.17;
15
+
16
+ /**
17
+ * @title Filtered Minter contract that allows tokens to be minted with ETH
18
+ * for addresses in a Merkle allowlist.
19
+ * This is designed to be used with GenArt721CoreContractV3 flagship or
20
+ * engine contracts.
21
+ * @author Art Blocks Inc.
22
+ * @notice Privileged Roles and Ownership:
23
+ * This contract is designed to be managed, with limited powers.
24
+ * Privileged roles and abilities are controlled by the project's artist, which
25
+ * can be modified by the core contract's Admin ACL contract. Both of these
26
+ * roles hold extensive power and can modify minter details.
27
+ * Care must be taken to ensure that the admin ACL contract and artist
28
+ * addresses are secure behind a multi-sig or other access control mechanism.
29
+ * ----------------------------------------------------------------------------
30
+ * The following functions are restricted to a project's artist:
31
+ * - updateMerkleRoot
32
+ * - updatePricePerTokenInWei
33
+ * - setProjectInvocationsPerAddress
34
+ * - setProjectMaxInvocations
35
+ * - manuallyLimitProjectMaxInvocations
36
+ * ----------------------------------------------------------------------------
37
+ * Additional admin and artist privileged roles may be described on other
38
+ * contracts that this minter integrates with.
39
+ * ----------------------------------------------------------------------------
40
+ * This contract allows vaults to configure token-level or wallet-level
41
+ * delegation of minting privileges. This allows a vault on an allowlist to
42
+ * delegate minting privileges to a wallet that is not on the allowlist,
43
+ * enabling the vault to remain air-gapped while still allowing minting. The
44
+ * delegation registry contract is responsible for managing these delegations,
45
+ * and is available at the address returned by the public immutable
46
+ * `delegationRegistryAddress`. At the time of writing, the delegation
47
+ * registry enables easy delegation configuring at https://delegate.cash/.
48
+ * Art Blocks does not guarentee the security of the delegation registry, and
49
+ * users should take care to ensure that the delegation registry is secure.
50
+ * Token-level delegations are configured by the vault owner, and contract-
51
+ * level delegations must be configured for the core token contract as returned
52
+ * by the public immutable variable `genArt721CoreAddress`.
53
+ */
54
+ contract MinterMerkleV5 is
55
+ ReentrancyGuard,
56
+ MinterBase,
57
+ IFilteredMinterMerkleV2
58
+ {
59
+ using MerkleProof for bytes32[];
60
+
61
+ /// Delegation registry address
62
+ address public immutable delegationRegistryAddress;
63
+
64
+ /// Delegation registry address
65
+ IDelegationRegistry private immutable delegationRegistryContract;
66
+
67
+ /// Core contract address this minter interacts with
68
+ address public immutable genArt721CoreAddress;
69
+
70
+ /// The core contract integrates with V3 contracts
71
+ IGenArt721CoreContractV3_Base private immutable genArtCoreContract_Base;
72
+
73
+ /// Minter filter address this minter interacts with
74
+ address public immutable minterFilterAddress;
75
+
76
+ /// Minter filter this minter may interact with.
77
+ IMinterFilterV0 private immutable minterFilter;
78
+
79
+ /// minterType for this minter
80
+ string public constant minterType = "MinterMerkleV5";
81
+
82
+ /// minter version for this minter
83
+ string public constant minterVersion = "v5.1.0";
84
+
85
+ /// project minter configuration keys used by this minter
86
+ bytes32 private constant CONFIG_MERKLE_ROOT = "merkleRoot";
87
+ bytes32 private constant CONFIG_USE_MAX_INVOCATIONS_PER_ADDRESS_OVERRIDE =
88
+ "useMaxMintsPerAddrOverride"; // shortened to fit in 32 bytes
89
+ bytes32 private constant CONFIG_MAX_INVOCATIONS_OVERRIDE =
90
+ "maxMintsPerAddrOverride"; // shortened to match format of previous key
91
+
92
+ uint256 constant ONE_MILLION = 1_000_000;
93
+
94
+ uint256 public constant DEFAULT_MAX_INVOCATIONS_PER_ADDRESS = 1;
95
+
96
+ struct ProjectConfig {
97
+ bool maxHasBeenInvoked;
98
+ bool priceIsConfigured;
99
+ // initial value is false, so by default, projects limit allowlisted
100
+ // addresses to a mint qty of `DEFAULT_MAX_INVOCATIONS_PER_ADDRESS`
101
+ bool useMaxInvocationsPerAddressOverride;
102
+ // a value of 0 means no limit
103
+ // (only used if `useMaxInvocationsPerAddressOverride` is true)
104
+ uint24 maxInvocationsPerAddressOverride;
105
+ uint24 maxInvocations;
106
+ uint256 pricePerTokenInWei;
107
+ }
108
+
109
+ mapping(uint256 => ProjectConfig) public projectConfig;
110
+
111
+ /// projectId => merkle root
112
+ mapping(uint256 => bytes32) public projectMerkleRoot;
113
+
114
+ /// projectId => purchaser address => qty of mints purchased for project
115
+ mapping(uint256 => mapping(address => uint256))
116
+ public projectUserMintInvocations;
117
+
118
+ modifier onlyArtist(uint256 _projectId) {
119
+ require(
120
+ msg.sender ==
121
+ genArtCoreContract_Base.projectIdToArtistAddress(_projectId),
122
+ "Only Artist"
123
+ );
124
+ _;
125
+ }
126
+
127
+ /**
128
+ * @notice Initializes contract to be a Filtered Minter for
129
+ * `_minterFilter`, integrated with Art Blocks core contract
130
+ * at address `_genArt721Address`. Also configures the delegation
131
+ * registry address to be address `_delegationRegistryAddress`.
132
+ * @param _genArt721Address Art Blocks core contract address for
133
+ * which this contract will be a minter.
134
+ * @param _minterFilter Minter filter for which this will be a
135
+ * filtered minter.
136
+ * @param _delegationRegistryAddress Delegation registry contract address.
137
+ */
138
+ constructor(
139
+ address _genArt721Address,
140
+ address _minterFilter,
141
+ address _delegationRegistryAddress
142
+ ) ReentrancyGuard() MinterBase(_genArt721Address) {
143
+ genArt721CoreAddress = _genArt721Address;
144
+ // always populate immutable engine contracts, but only use appropriate
145
+ // interface based on isEngine in the rest of the contract
146
+ genArtCoreContract_Base = IGenArt721CoreContractV3_Base(
147
+ _genArt721Address
148
+ );
149
+ delegationRegistryAddress = _delegationRegistryAddress;
150
+ emit DelegationRegistryUpdated(_delegationRegistryAddress);
151
+ delegationRegistryContract = IDelegationRegistry(
152
+ _delegationRegistryAddress
153
+ );
154
+ minterFilterAddress = _minterFilter;
155
+ minterFilter = IMinterFilterV0(_minterFilter);
156
+ require(
157
+ minterFilter.genArt721CoreAddress() == _genArt721Address,
158
+ "Illegal contract pairing"
159
+ );
160
+ // broadcast default max invocations per address for this minter
161
+ emit DefaultMaxInvocationsPerAddress(
162
+ DEFAULT_MAX_INVOCATIONS_PER_ADDRESS
163
+ );
164
+ }
165
+
166
+ /**
167
+ * @notice Update the Merkle root for project `_projectId`.
168
+ * @param _projectId Project ID to be updated.
169
+ * @param _root root of Merkle tree defining addresses allowed to mint
170
+ * on project `_projectId`.
171
+ */
172
+ function updateMerkleRoot(
173
+ uint256 _projectId,
174
+ bytes32 _root
175
+ ) external onlyArtist(_projectId) {
176
+ require(_root != bytes32(0), "Root must be provided");
177
+ projectMerkleRoot[_projectId] = _root;
178
+ emit ConfigValueSet(_projectId, CONFIG_MERKLE_ROOT, _root);
179
+ }
180
+
181
+ /**
182
+ * @notice Returns hashed address (to be used as merkle tree leaf).
183
+ * Included as a public function to enable users to calculate their hashed
184
+ * address in Solidity when generating proofs off-chain.
185
+ * @param _address address to be hashed
186
+ * @return bytes32 hashed address, via keccak256 (using encodePacked)
187
+ */
188
+ function hashAddress(address _address) public pure returns (bytes32) {
189
+ return keccak256(abi.encodePacked(_address));
190
+ }
191
+
192
+ /**
193
+ * @notice Verify if address is allowed to mint on project `_projectId`.
194
+ * @param _projectId Project ID to be checked.
195
+ * @param _proof Merkle proof for address.
196
+ * @param _address Address to check.
197
+ * @return inAllowlist true only if address is allowed to mint and valid
198
+ * Merkle proof was provided
199
+ */
200
+ function verifyAddress(
201
+ uint256 _projectId,
202
+ bytes32[] calldata _proof,
203
+ address _address
204
+ ) public view returns (bool) {
205
+ return
206
+ _proof.verifyCalldata(
207
+ projectMerkleRoot[_projectId],
208
+ hashAddress(_address)
209
+ );
210
+ }
211
+
212
+ /**
213
+ * @notice Sets maximum allowed invocations per allowlisted address for
214
+ * project `_project` to `limit`. If `limit` is set to 0, allowlisted
215
+ * addresses will be able to mint as many times as desired, until the
216
+ * project reaches its maximum invocations.
217
+ * Default is a value of 1 if never configured by artist.
218
+ * @param _projectId Project ID to toggle the mint limit.
219
+ * @param _maxInvocationsPerAddress Maximum allowed invocations per
220
+ * allowlisted address.
221
+ * @dev default value stated above must be updated if the value of
222
+ * CONFIG_USE_MAX_INVOCATIONS_PER_ADDRESS_OVERRIDE is changed.
223
+ */
224
+ function setProjectInvocationsPerAddress(
225
+ uint256 _projectId,
226
+ uint24 _maxInvocationsPerAddress
227
+ ) external onlyArtist(_projectId) {
228
+ ProjectConfig storage _projectConfig = projectConfig[_projectId];
229
+ // use override value instead of the contract's default
230
+ // @dev this never changes from true to false; default value is only
231
+ // used if artist has never configured project invocations per address
232
+ _projectConfig.useMaxInvocationsPerAddressOverride = true;
233
+ // update the override value
234
+ _projectConfig
235
+ .maxInvocationsPerAddressOverride = _maxInvocationsPerAddress;
236
+ // generic events
237
+ emit ConfigValueSet(
238
+ _projectId,
239
+ CONFIG_USE_MAX_INVOCATIONS_PER_ADDRESS_OVERRIDE,
240
+ true
241
+ );
242
+ emit ConfigValueSet(
243
+ _projectId,
244
+ CONFIG_MAX_INVOCATIONS_OVERRIDE,
245
+ uint256(_maxInvocationsPerAddress)
246
+ );
247
+ }
248
+
249
+ /**
250
+ * @notice Syncs local maximum invocations of project `_projectId` based on
251
+ * the value currently defined in the core contract.
252
+ * @param _projectId Project ID to set the maximum invocations for.
253
+ * @dev this enables gas reduction after maxInvocations have been reached -
254
+ * core contracts shall still enforce a maxInvocation check during mint.
255
+ */
256
+ function setProjectMaxInvocations(
257
+ uint256 _projectId
258
+ ) public onlyArtist(_projectId) {
259
+ uint256 maxInvocations;
260
+ uint256 invocations;
261
+ (invocations, maxInvocations, , , , ) = genArtCoreContract_Base
262
+ .projectStateData(_projectId);
263
+ // update storage with results
264
+ projectConfig[_projectId].maxInvocations = uint24(maxInvocations);
265
+
266
+ // We need to ensure maxHasBeenInvoked is correctly set after manually syncing the
267
+ // local maxInvocations value with the core contract's maxInvocations value.
268
+ // This synced value of maxInvocations from the core contract will always be greater
269
+ // than or equal to the previous value of maxInvocations stored locally.
270
+ projectConfig[_projectId].maxHasBeenInvoked =
271
+ invocations == maxInvocations;
272
+
273
+ emit ProjectMaxInvocationsLimitUpdated(_projectId, maxInvocations);
274
+ }
275
+
276
+ /**
277
+ * @notice Manually sets the local maximum invocations of project `_projectId`
278
+ * with the provided `_maxInvocations`, checking that `_maxInvocations` is less
279
+ * than or equal to the value of project `_project_id`'s maximum invocations that is
280
+ * set on the core contract.
281
+ * @dev Note that a `_maxInvocations` of 0 can only be set if the current `invocations`
282
+ * value is also 0 and this would also set `maxHasBeenInvoked` to true, correctly short-circuiting
283
+ * this minter's purchase function, avoiding extra gas costs from the core contract's maxInvocations check.
284
+ * @param _projectId Project ID to set the maximum invocations for.
285
+ * @param _maxInvocations Maximum invocations to set for the project.
286
+ */
287
+ function manuallyLimitProjectMaxInvocations(
288
+ uint256 _projectId,
289
+ uint256 _maxInvocations
290
+ ) external onlyArtist(_projectId) {
291
+ // CHECKS
292
+ // ensure that the manually set maxInvocations is not greater than what is set on the core contract
293
+ uint256 maxInvocations;
294
+ uint256 invocations;
295
+ (invocations, maxInvocations, , , , ) = genArtCoreContract_Base
296
+ .projectStateData(_projectId);
297
+ require(
298
+ _maxInvocations <= maxInvocations,
299
+ "Cannot increase project max invocations above core contract set project max invocations"
300
+ );
301
+ require(
302
+ _maxInvocations >= invocations,
303
+ "Cannot set project max invocations to less than current invocations"
304
+ );
305
+
306
+ // EFFECTS
307
+ // update storage with results
308
+ projectConfig[_projectId].maxInvocations = uint24(_maxInvocations);
309
+ // We need to ensure maxHasBeenInvoked is correctly set after manually setting the
310
+ // local maxInvocations value.
311
+ projectConfig[_projectId].maxHasBeenInvoked =
312
+ invocations == _maxInvocations;
313
+
314
+ emit ProjectMaxInvocationsLimitUpdated(_projectId, _maxInvocations);
315
+ }
316
+
317
+ /**
318
+ * @notice Warning: Disabling purchaseTo is not supported on this minter.
319
+ * This method exists purely for interface-conformance purposes.
320
+ */
321
+ function togglePurchaseToDisabled(
322
+ uint256 _projectId
323
+ ) external view onlyArtist(_projectId) {
324
+ revert("Action not supported");
325
+ }
326
+
327
+ /**
328
+ * @notice projectId => has project reached its maximum number of
329
+ * invocations? Note that this returns a local cache of the core contract's
330
+ * state, and may be out of sync with the core contract. This is
331
+ * intentional, as it only enables gas optimization of mints after a
332
+ * project's maximum invocations has been reached. A false negative will
333
+ * only result in a gas cost increase, since the core contract will still
334
+ * enforce a maxInvocation check during minting. A false positive is not
335
+ * possible because the V3 core contract only allows maximum invocations
336
+ * to be reduced, not increased. Based on this rationale, we intentionally
337
+ * do not do input validation in this method as to whether or not the input
338
+ * `_projectId` is an existing project ID.
339
+ */
340
+ function projectMaxHasBeenInvoked(
341
+ uint256 _projectId
342
+ ) external view returns (bool) {
343
+ return projectConfig[_projectId].maxHasBeenInvoked;
344
+ }
345
+
346
+ /**
347
+ * @notice projectId => project's maximum number of invocations.
348
+ * Optionally synced with core contract value, for gas optimization.
349
+ * Note that this returns a local cache of the core contract's
350
+ * state, and may be out of sync with the core contract. This is
351
+ * intentional, as it only enables gas optimization of mints after a
352
+ * project's maximum invocations has been reached.
353
+ * @dev A number greater than the core contract's project max invocations
354
+ * will only result in a gas cost increase, since the core contract will
355
+ * still enforce a maxInvocation check during minting. A number less than
356
+ * the core contract's project max invocations is only possible when the
357
+ * project's max invocations have not been synced on this minter, since the
358
+ * V3 core contract only allows maximum invocations to be reduced, not
359
+ * increased. When this happens, the minter will enable minting, allowing
360
+ * the core contract to enforce the max invocations check. Based on this
361
+ * rationale, we intentionally do not do input validation in this method as
362
+ * to whether or not the input `_projectId` is an existing project ID.
363
+ */
364
+ function projectMaxInvocations(
365
+ uint256 _projectId
366
+ ) external view returns (uint256) {
367
+ return uint256(projectConfig[_projectId].maxInvocations);
368
+ }
369
+
370
+ /**
371
+ * @notice Updates this minter's price per token of project `_projectId`
372
+ * to be '_pricePerTokenInWei`, in Wei.
373
+ * This price supersedes any legacy core contract price per token value.
374
+ * @dev Note that it is intentionally supported here that the configured
375
+ * price may be explicitly set to `0`.
376
+ */
377
+ function updatePricePerTokenInWei(
378
+ uint256 _projectId,
379
+ uint256 _pricePerTokenInWei
380
+ ) external onlyArtist(_projectId) {
381
+ ProjectConfig storage _projectConfig = projectConfig[_projectId];
382
+ _projectConfig.pricePerTokenInWei = _pricePerTokenInWei;
383
+ _projectConfig.priceIsConfigured = true;
384
+ emit PricePerTokenInWeiUpdated(_projectId, _pricePerTokenInWei);
385
+
386
+ // sync local max invocations if not initially populated
387
+ // @dev if local max invocations and maxHasBeenInvoked are both
388
+ // initial values, we know they have not been populated.
389
+ if (
390
+ _projectConfig.maxInvocations == 0 &&
391
+ _projectConfig.maxHasBeenInvoked == false
392
+ ) {
393
+ setProjectMaxInvocations(_projectId);
394
+ }
395
+ }
396
+
397
+ /**
398
+ * @notice Inactive function - requires Merkle proof to purchase.
399
+ */
400
+ function purchase(uint256) external payable returns (uint256) {
401
+ revert("Must provide Merkle proof");
402
+ }
403
+
404
+ /**
405
+ * @notice Inactive function - requires Merkle proof to purchase.
406
+ */
407
+ function purchaseTo(address, uint256) public payable returns (uint256) {
408
+ revert("Must provide Merkle proof");
409
+ }
410
+
411
+ /**
412
+ * @notice Purchases a token from project `_projectId`.
413
+ * @param _projectId Project ID to mint a token on.
414
+ * @param _proof Merkle proof.
415
+ * @return tokenId Token ID of minted token
416
+ */
417
+ function purchase(
418
+ uint256 _projectId,
419
+ bytes32[] calldata _proof
420
+ ) external payable returns (uint256 tokenId) {
421
+ tokenId = purchaseTo_kem(msg.sender, _projectId, _proof, address(0));
422
+ return tokenId;
423
+ }
424
+
425
+ /**
426
+ * @notice gas-optimized version of purchase(uint256,bytes32[]).
427
+ */
428
+ function purchase_gD5(
429
+ uint256 _projectId,
430
+ bytes32[] calldata _proof
431
+ ) external payable returns (uint256 tokenId) {
432
+ tokenId = purchaseTo_kem(msg.sender, _projectId, _proof, address(0));
433
+ return tokenId;
434
+ }
435
+
436
+ /**
437
+ * @notice Purchases a token from project `_projectId` and sets
438
+ * the token's owner to `_to`.
439
+ * @param _to Address to be the new token's owner.
440
+ * @param _projectId Project ID to mint a token on.
441
+ * @param _proof Merkle proof.
442
+ * @return tokenId Token ID of minted token
443
+ */
444
+ function purchaseTo(
445
+ address _to,
446
+ uint256 _projectId,
447
+ bytes32[] calldata _proof
448
+ ) external payable returns (uint256 tokenId) {
449
+ return purchaseTo_kem(_to, _projectId, _proof, address(0));
450
+ }
451
+
452
+ /**
453
+ * @notice Purchases a token from project `_projectId` and sets
454
+ * the token's owner to `_to`, as a delegate, (the `msg.sender`)
455
+ * on behalf of an explicitly defined vault.
456
+ * @param _to Address to be the new token's owner.
457
+ * @param _projectId Project ID to mint a token on.
458
+ * @param _proof Merkle proof.
459
+ * @param _vault Vault being purchased on behalf of.
460
+ * @return tokenId Token ID of minted token
461
+ */
462
+ function purchaseTo(
463
+ address _to,
464
+ uint256 _projectId,
465
+ bytes32[] calldata _proof,
466
+ address _vault
467
+ ) external payable returns (uint256 tokenId) {
468
+ return purchaseTo_kem(_to, _projectId, _proof, _vault);
469
+ }
470
+
471
+ /**
472
+ * @notice gas-optimized version of
473
+ * purchaseTo(address,uint256,bytes32[],address).
474
+ * @param _to Address to be the new token's owner.
475
+ * @param _projectId Project ID to mint a token on.
476
+ * @param _proof Merkle proof. Must be a valid proof of either `msg.sender`
477
+ * if `_vault` is `address(0)`, or `_vault` if `_vault` is not `address(0)`.
478
+ * @param _vault Vault being purchased on behalf of. Acceptable to be
479
+ * address(0) if no vault.
480
+ */
481
+ function purchaseTo_kem(
482
+ address _to,
483
+ uint256 _projectId,
484
+ bytes32[] calldata _proof,
485
+ address _vault // acceptable to be `address(0)` if no vault
486
+ ) public payable nonReentrant returns (uint256 tokenId) {
487
+ // CHECKS
488
+ ProjectConfig storage _projectConfig = projectConfig[_projectId];
489
+
490
+ // Note that `maxHasBeenInvoked` is only checked here to reduce gas
491
+ // consumption after a project has been fully minted.
492
+ // `_projectConfig.maxHasBeenInvoked` is locally cached to reduce
493
+ // gas consumption, but if not in sync with the core contract's value,
494
+ // the core contract also enforces its own max invocation check during
495
+ // minting.
496
+ require(
497
+ !_projectConfig.maxHasBeenInvoked,
498
+ "Maximum number of invocations reached"
499
+ );
500
+
501
+ // load price of token into memory
502
+ uint256 pricePerTokenInWei = _projectConfig.pricePerTokenInWei;
503
+
504
+ require(
505
+ msg.value >= pricePerTokenInWei,
506
+ "Must send minimum value to mint!"
507
+ );
508
+
509
+ // require artist to have configured price of token on this minter
510
+ require(_projectConfig.priceIsConfigured, "Price not configured");
511
+
512
+ // no contract filter since Merkle tree controls allowed addresses
513
+
514
+ // NOTE: delegate-vault handling **begins here**.
515
+
516
+ // handle that the vault may be either the `msg.sender` in the case
517
+ // that there is not a true vault, or may be `_vault` if one is
518
+ // provided explicitly (and it is valid).
519
+ address vault = msg.sender;
520
+ if (_vault != address(0)) {
521
+ // If a vault is provided, it must be valid, otherwise throw rather
522
+ // than optimistically-minting with original `msg.sender`.
523
+ // Note, we do not check `checkDelegateForAll` as well, as it is known
524
+ // to be implicitly checked by calling `checkDelegateForContract`.
525
+ bool isValidDelegee = delegationRegistryContract
526
+ .checkDelegateForContract(
527
+ msg.sender, // delegate
528
+ _vault, // vault
529
+ genArt721CoreAddress // contract
530
+ );
531
+ require(isValidDelegee, "Invalid delegate-vault pairing");
532
+ vault = _vault;
533
+ }
534
+
535
+ // require valid Merkle proof
536
+ require(
537
+ verifyAddress(_projectId, _proof, vault),
538
+ "Invalid Merkle proof"
539
+ );
540
+
541
+ // limit mints per address by project
542
+ uint256 _maxProjectInvocationsPerAddress = _projectConfig
543
+ .useMaxInvocationsPerAddressOverride
544
+ ? _projectConfig.maxInvocationsPerAddressOverride
545
+ : DEFAULT_MAX_INVOCATIONS_PER_ADDRESS;
546
+
547
+ // note that mint limits index off of the `vault` (when applicable)
548
+ require(
549
+ projectUserMintInvocations[_projectId][vault] <
550
+ _maxProjectInvocationsPerAddress ||
551
+ _maxProjectInvocationsPerAddress == 0,
552
+ "Maximum number of invocations per address reached"
553
+ );
554
+
555
+ // EFFECTS
556
+ // increment user's invocations for this project
557
+ unchecked {
558
+ // this will never overflow since user's invocations on a project
559
+ // are limited by the project's max invocations
560
+ projectUserMintInvocations[_projectId][vault]++;
561
+ }
562
+
563
+ // mint token
564
+ tokenId = minterFilter.mint(_to, _projectId, vault);
565
+
566
+ // NOTE: delegate-vault handling **ends here**.
567
+
568
+ // invocation is token number plus one, and will never overflow due to
569
+ // limit of 1e6 invocations per project. block scope for gas efficiency
570
+ // (i.e. avoid an unnecessary var initialization to 0).
571
+ unchecked {
572
+ uint256 tokenInvocation = (tokenId % ONE_MILLION) + 1;
573
+ uint256 localMaxInvocations = _projectConfig.maxInvocations;
574
+ // handle the case where the token invocation == minter local max
575
+ // invocations occurred on a different minter, and we have a stale
576
+ // local maxHasBeenInvoked value returning a false negative.
577
+ // @dev this is a CHECK after EFFECTS, so security was considered
578
+ // in detail here.
579
+ require(
580
+ tokenInvocation <= localMaxInvocations,
581
+ "Maximum invocations reached"
582
+ );
583
+ // in typical case, update the local maxHasBeenInvoked value
584
+ // to true if the token invocation == minter local max invocations
585
+ // (enables gas efficient reverts after sellout)
586
+ if (tokenInvocation == localMaxInvocations) {
587
+ _projectConfig.maxHasBeenInvoked = true;
588
+ }
589
+ }
590
+
591
+ // INTERACTIONS
592
+ splitFundsETH(_projectId, pricePerTokenInWei, genArt721CoreAddress);
593
+
594
+ return tokenId;
595
+ }
596
+
597
+ /**
598
+ * @notice projectId => maximum invocations per allowlisted address. If a
599
+ * a value of 0 is returned, there is no limit on the number of mints per
600
+ * allowlisted address.
601
+ * Default behavior is limit 1 mint per address.
602
+ * This value can be changed at any time by the artist.
603
+ * @dev default value stated above must be updated if the value of
604
+ * CONFIG_USE_MAX_INVOCATIONS_PER_ADDRESS_OVERRIDE is changed.
605
+ */
606
+ function projectMaxInvocationsPerAddress(
607
+ uint256 _projectId
608
+ ) public view returns (uint256) {
609
+ ProjectConfig storage _projectConfig = projectConfig[_projectId];
610
+ if (_projectConfig.useMaxInvocationsPerAddressOverride) {
611
+ return uint256(_projectConfig.maxInvocationsPerAddressOverride);
612
+ } else {
613
+ return DEFAULT_MAX_INVOCATIONS_PER_ADDRESS;
614
+ }
615
+ }
616
+
617
+ /**
618
+ * @notice Returns remaining invocations for a given address.
619
+ * If `projectLimitsMintInvocationsPerAddress` is false, individual
620
+ * addresses are only limited by the project's maximum invocations, and a
621
+ * dummy value of zero is returned for `mintInvocationsRemaining`.
622
+ * If `projectLimitsMintInvocationsPerAddress` is true, the quantity of
623
+ * remaining mint invocations for address `_address` is returned as
624
+ * `mintInvocationsRemaining`.
625
+ * Note that mint invocations per address can be changed at any time by the
626
+ * artist of a project.
627
+ * Also note that all mint invocations are limited by a project's maximum
628
+ * invocations as defined on the core contract. This function may return
629
+ * a value greater than the project's remaining invocations.
630
+ */
631
+ function projectRemainingInvocationsForAddress(
632
+ uint256 _projectId,
633
+ address _address
634
+ )
635
+ external
636
+ view
637
+ returns (
638
+ bool projectLimitsMintInvocationsPerAddress,
639
+ uint256 mintInvocationsRemaining
640
+ )
641
+ {
642
+ uint256 maxInvocationsPerAddress = projectMaxInvocationsPerAddress(
643
+ _projectId
644
+ );
645
+ if (maxInvocationsPerAddress == 0) {
646
+ // project does not limit mint invocations per address, so leave
647
+ // `projectLimitsMintInvocationsPerAddress` at solidity initial
648
+ // value of false. Also leave `mintInvocationsRemaining` at
649
+ // solidity initial value of zero, as indicated in this function's
650
+ // documentation.
651
+ } else {
652
+ projectLimitsMintInvocationsPerAddress = true;
653
+ uint256 userMintInvocations = projectUserMintInvocations[
654
+ _projectId
655
+ ][_address];
656
+ // if user has not reached max invocations per address, return
657
+ // remaining invocations
658
+ if (maxInvocationsPerAddress > userMintInvocations) {
659
+ unchecked {
660
+ // will never underflow due to the check above
661
+ mintInvocationsRemaining =
662
+ maxInvocationsPerAddress -
663
+ userMintInvocations;
664
+ }
665
+ }
666
+ // else user has reached their maximum invocations, so leave
667
+ // `mintInvocationsRemaining` at solidity initial value of zero
668
+ }
669
+ }
670
+
671
+ /**
672
+ * @notice Process proof for an address. Returns Merkle root. Included to
673
+ * enable users to easily verify a proof's validity.
674
+ * @param _proof Merkle proof for address.
675
+ * @param _address Address to process.
676
+ * @return merkleRoot Merkle root for `_address` and `_proof`
677
+ */
678
+ function processProofForAddress(
679
+ bytes32[] calldata _proof,
680
+ address _address
681
+ ) external pure returns (bytes32) {
682
+ return _proof.processProofCalldata(hashAddress(_address));
683
+ }
684
+
685
+ /**
686
+ * @notice Gets if price of token is configured, price of minting a
687
+ * token on project `_projectId`, and currency symbol and address to be
688
+ * used as payment. Supersedes any core contract price information.
689
+ * @param _projectId Project ID to get price information for.
690
+ * @return isConfigured true only if token price has been configured on
691
+ * this minter
692
+ * @return tokenPriceInWei current price of token on this minter - invalid
693
+ * if price has not yet been configured
694
+ * @return currencySymbol currency symbol for purchases of project on this
695
+ * minter. This minter always returns "ETH"
696
+ * @return currencyAddress currency address for purchases of project on
697
+ * this minter. This minter always returns null address, reserved for ether
698
+ */
699
+ function getPriceInfo(
700
+ uint256 _projectId
701
+ )
702
+ external
703
+ view
704
+ returns (
705
+ bool isConfigured,
706
+ uint256 tokenPriceInWei,
707
+ string memory currencySymbol,
708
+ address currencyAddress
709
+ )
710
+ {
711
+ ProjectConfig storage _projectConfig = projectConfig[_projectId];
712
+ isConfigured = _projectConfig.priceIsConfigured;
713
+ tokenPriceInWei = _projectConfig.pricePerTokenInWei;
714
+ currencySymbol = "ETH";
715
+ currencyAddress = address(0);
716
+ }
717
+ }