Gas optimized, royalty enforcing version of the ERC-721 NFT standard. Bootstrapped using, ERC721A, ClosedSea & Foundry.
-
Start or pause contract at will
-
Set a next start time to provide real time countdown to UI
-
Set the current phase to ensure real time updates to UI
-
Modify hidden URI at will before reveal
-
Set whitelists for 3 of the mint phases (Arcana/Aspirant/Alliance)
-
Modify token baseURI at will after reveal
-
Costless mint of Arcana Treasury reserves into a wallet of choice.
-
Gas optimised, payable & secure minting for each phase (Arcana, Aspirant, Alliance, Public)
-
Hard cap on total mintable by each wallet for each phase
-
Function to retrieve current number minted for each state
-
Install Foundry
-
Clone arcana-nft-solidity repository
git clone https://github.com/Prominence-Games/arcana-nft-solidity.git
- Change directory into the root of project
cd /arcana-nft-solidity
- Install Solidity dependencies
forge install
- Install npm dependencies
yarn
OR
npm i
src
ββ ArcanaPrime.sol β "ARCANA (ARC) Smart Contract"
test
ββ ArcanaPrime.t.sol β "ARCANA (ARC) Smart Contract Tests"
ββ helpers
ββ Merkle.sol β "Mock merkle root hash generation in smart contract"
ββ MurkyBase.sol β "Verify merkle root and leaf hashes"
To run tests:
forge test -vvvvv
enum Phases{ CLOSED, ARCANA, ASPIRANT, ALLIANCE, PUBLIC }
The Phases enum declares the different phases of minting of the ARC NFTs.
Constants are set once during initiation and cannot be changed after.
uint256 public constant WAR_CHEST_SUPPLY = 512;
This constant represents the community reserves of Arcana which will be minted into a community wallet during the CLOSED phase.
uint256 public constant MAX_ENTITLEMENTS_ALLOWED = 2;
This constant represents the maximum allowed ARCs per wallet cumulated across the whitelist phases (ARCANA, ASPIRANT, ALLIANCE).
uint256 public constant MAX_QUANTITY_ALLOWED = 3;
This constant represents the maximum allowed ARCs per wallet minted during the PUBLIC phase.
uint256 public constant MAX_SUPPLY = 10_000;
This constant represents the maximum allowed quantity to be minted. Once the totalSupply() reaches the MAX_SUPPLY, mint functions will revert.
uint256 public constant MINT_PRICE = 0.08 ether;
This constant represents the value of the transaction to be set when the mint functions are called from the client.
uint public currentPhase;
This public variable allows the client to retrieve the current phase of the mint.
string public notRevealedUri;
This public variable allows the client to retrieve the metadata of ARCANAs before the art is revealed.
string public baseTokenURI;
This public variable allows the client to retrieve the metadata of ARCANAs after the art is revealed
uint256 public nextStartTime;
This public variable is the UNIX timestamp of next start time which is reflected on the mint site UI.
bool public paused = true;
This public variable is the boolean flag to toggle the smart contract mintability on and off after the start and end of each phase.
bytes32 public arcanaListMerkleRoot;
This public variable holds the merkle root hash of the whitelisted addresses of the ARCANA phase.
bytes32 public aspirantListMerkleRoot;
This public variable holds the merkle root hash of the whitelisted addresses of the ASPIRANT phase.
bytes32 public allianceListMerkleRoot;
This public variable holds the merkle root hash of the whitelisted addresses of the ALLIANCE phase.
bool public isTransfused = false;
This public variable is the boolean flag to indicate the state of the art reveal. True means revealed and the metadata retrieved by the client will reflect that. Default is false.
uint256 public scheduledTransfusionTime;
This public variable indicates a future block number for the sequence offset to take place.
uint256 public sequenceOffset;
The sequence offset is used to randomly assign metadata to each ARCANA in a provably fair manner. There is no favouristism in the Promisphere.
string public dna;
The dna is a public attestation of the sequence in which the metadata were generated. With the same input and sequence, this value should always compute to be the same.
mapping(bytes32 => bool) public nonceRegistry;
The nonce registry is introduced as a security feature used in the PUBLIC phase to prevent bad actors from replaying transactions.
function registerCustomBlacklist(address subscriptionOrRegistrantToCopy, bool subscribe) public onlyOwner {
This function allows us to use a custom version of the filter registry in case OpenSea has any monopolistic tendencies.
The default OpenSea curated block list, _registerForOperatorFiltering()
, is invoked without arguments.
function setArcanaListMerkleRoot(bytes32 _merkleRootHash) external onlyOwner
Method used to update the merkle root hash arcanaListMerkleRoot
generated from a list of addresses in the ARCANA phase.
function setArcanaListMerkleRoot(bytes32 _merkleRootHash) external onlyOwner
Method used to update the merkle root hash aspirantListMerkleRoot
generated from a list of addresses in the ASPIRANT phase.
function setAllianceListMerkleRoot(bytes32 _merkleRootHash) external onlyOwner
Method used to update the merkle root hash allianceListMerkleRoot
generated from a list of addresses in the ALLIANCE phase.
function setNotRevealedBaseURI(string memory _baseURI) external onlyOwner
Method used to update the pointer (notRevealedUri
) to the metadata file before art is revealed.
function togglePause(bool _state) external payable onlyOwner
Method used to start or stop mint functionality (pause
) of the smart contract at the end of each phase to prevent front-running.
function setNextStartTime(uint256 _timestamp) external payable onlyOwner
Method used to update the nextStartTime
. The UNIX value retrieved is used to display a countdown timer on the client.
function setCurrentPhase(uint index) external payable onlyOwner
Method used to set the current phase. The state of each phase (currentPhase
) is displayed clearly in the UI to users.
function setBaseTokenURI(string memory _baseURI) external onlyOwner
Method used to set the pointer (baseTokenURI
) of the metadata JSON post reveal.
function commitDNASequence(string calldata _dna) external payable onlyOwner
Method used to set the scheduledTransfusionTime
and the dna
sequence. Can only be called once to prevent foul play by contract owner.
function transfuse() external payable onlyOwner
Method used to randomly assign metadata to each token id via the sequenceOffset
variable. Can only be called once and after commitDNASequence
is invoked.
function mintWarChestReserve(address _communityWalletPublicKey) external payable isBelowMaxSupply(WAR_CHEST_SUPPLY) onlyOwner
Method used to mint WAR_CHEST_SUPPLY
pieces of ARCANAs into the community reserve into an address of our choosing by the contract owner.
function mintArcanaList(bytes32[] calldata _merkleProof, uint256 _quantity) external payable isBelowMaxSupply(_quantity) isWhitelisted(_merkleProof, arcanaListMerkleRoot) isNotPaused isMintOpen(Phases.ARCANA)
Method invoked by the client during the ARCANA phase. Modifiers used to check that there is enough supply, address is whitelisted, contract is not paused and the current phase is correct. Internally that checks enough ETH is paid and that the entitlement restriction for the phase is not exceeded.
function mintAspirantList(bytes32[] calldata _merkleProof, uint256 _quantity) external payable isBelowMaxSupply(_quantity) isWhitelisted(_merkleProof, aspirantListMerkleRoot) isNotPaused isMintOpen(Phases.ASPIRANT)
Method invoked by the client during the ASPIRANT phase. Modifiers used to check that there is enough supply, address is whitelisted, contract is not paused and the current phase is correct. Internally that checks enough ETH is paid and that the entitlement restriction for the phase is not exceeded.
function mintAllianceList(bytes32[] calldata _merkleProof, uint256 _quantity) external payable isBelowMaxSupply(_quantity) isWhitelisted(_merkleProof, arcanaListMerkleRoot) isNotPaused isMintOpen(Phases.ARCANA)
Method invoked by the client during the ALLIANCE phase. Modifiers used to check that there is enough supply, address is whitelisted, contract is not paused and the current phase is correct. Internally that checks enough ETH is paid and that the entitlement restriction for the phase is not exceeded.
function mintPublic(uint256 _quantity, bytes32 _nonce, bytes32 _hash, uint8 v, bytes32 r, bytes32 s) external payable isBelowMaxSupply(_quantity) isNotPaused isMintOpen(Phases.PUBLIC)
Method invoked by the client during the PUBLIC phase. Modifiers used to check that there is enough supply, contract is not paused and the current phase is correct. Internally checks that the signed nonce sent from the client as a function parameter is the same as the one that is generated on nthe fly. Farthermore, the nonce used must not be used before to prevent replay attacks. Also checks that value of transaction matches the quantity minted and that the quantity restriction for the PUBLIC phase is not exceeded.
function tokenURI(uint256 _tokenId) public view override returns (string memory)
Overrides the super function that is used by NFT marketplaces to retrieve metadata about the token. The contruct of this changes pre-reveal and post-reveal.
function getBits(uint256 _input, uint256 _startBit, uint256 _length) private pure returns (uint256)
Gas optimised way to retrieve the total tokens minted by a wallet at each phase.
function getTotalEntitlements(address _minter) public view returns (uint256)
Gas optimised way to retrieve the cumulative tokens minted across the whitelist phases (ARCANA, ASPIRANT, ALLIANCE)
function getPublicListMints(address _minter) public view returns (uint256)
Gas optimised way to retrive the total tokens minted in the PUBLIC phase.
error MaxQuantityAllowedExceeded();
Error reverted when the max quantity allowed in the PUBLIC mint phase is exceeded.
error MaxEntitlementsExceeded();
Error reverted when the cumulative mints exceeds the MAX_ENTITLEMENTS_ALLOWED
value across the whitelist phases (ARCANA, ASPIRANT, ALLIANCE)
error MaxSupplyExceeded();
Error reverted when the sum of the totalSupply()
+ quantity
is more than or equals to the MAX_SUPPLY
error ContractIsPaused();
Error reverted when the smart contract is paused but any one of the four public phases mint methods are called. mintArcanaList
, mintAspirantList
, mintAllianceList
, publicMint
error PriceIncorrect();
Error reverted when value set for the transaction falls below the quantity
X MINT_PRICE
.
error ContractsNotAllowed();
Error reverted when a smart contract is used to mint the public phase.
error NonceConsumed();
Error reverted when a used nonce that is found in the nonceRegistry
is reused in the publicMint
function. This is to prevent replay attacks.
error HashMismatched();
Error reverted when the hash calculated onchain is not the same as the one calculated by the client.
error MerkleProofInvalid();
Error reverted when merkle proof hash of the leaf node sent from the client is not part of the markle tree hash proof. This means that the invoker is not part of the whitelist.
error SignedHashMismatched();
Error reverted when the signed hash calculated onchain is not the same as the one calculated by the client. This means that the invoker of the method is not the same as the once who signed it.
error MintIsNotOpen();
Error reverted when the pause
is false but any one of the four mint functions are invoked. mintArcanaList
, mintAspirantList
, mintAllianceList
, publicMint
error DNASequenceHaveBeenInitialised();
Error reverted when the scheduledTransfusionTime
has already been set once.
error DNASequenceNotSubmitted();
Error reverted when the scheduledTransfusionTime
has not been set. commitDNASequence
needs to be invoked first.
error NotReadyForTranfusion();
Error reverted when the block.number
< scheduledTransfusionTime
. This value needs block.number
needs to be less than 256
blocks from scheduledTransfusionTime
or transfusion will break.
error TransfusionSequenceCompleted();
Error reverted when the isTransfused
flag is set to true. This means that the transfuse
function has already been successfully invoked once.
modifier isMintOpen(Phases phase)
Modifier to guard a function and revert if currentPhase
is not equal to the unint(phase)
. This means that the wrong mint function is invoked in the wrong phase.
modifier isNotPaused()
Modifier to guard a function from minting when the contract is paused.
modifier isBelowMaxSupply(uint256 _amount)
Modifier to guard function from executing if the totalSupply()
+ _amount
> MAX_SUPPLY
. This is introduced to ensure that no more than MAX_SUPPLY
is minted.
modifier isWhitelisted(bytes32[] calldata _merkleProof, bytes32 _merkleRoot)
Modifer checks the merkle proof of each node against the merkle tree root hash. If the node is not part of the tree, the mint function is prevented from executing and reverts with an error.
Special thanks to:
- vectorized.eth (ClosedSea) - @optimizoor
- chiru-labs (ERC721A) - @chiru-labs
- Paradigm (Foundry) - @Foundry