├── .gitignore ├── metadata.json ├── merkle_tree ├── whitelistAddress.js └── index.js ├── README.md ├── scripts └── deploy.js ├── hardhat.config.js ├── package.json ├── contracts └── ApeGoddess.sol └── test ├── Lock.js └── ApeGoddess.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | 12 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ape Goddess", 3 | "description": "Ape Goddess is an NFT collection for degens", 4 | "image": "ipfs://QmSgx9wr47LxsU5rz5r42XvTEn48S6WbCqTPETJc3WKHWG" 5 | } -------------------------------------------------------------------------------- /merkle_tree/whitelistAddress.js: -------------------------------------------------------------------------------- 1 | const whitelistAddress = [ 2 | "0x0c39E34505beC37A0e740Ca00804D27De640b9f6", 3 | "0x81dbE0486ce6274984dF3b575911757E5522dc7E", 4 | "0xE9ce61869e48A1a3B9Be1FB2b31B426E38159168", 5 | "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4" 6 | ] 7 | module.exports = { whitelistAddress } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample Hardhat Project 2 | 3 | This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract. 4 | 5 | Try running some of the following tasks: 6 | 7 | ```shell 8 | npx hardhat help 9 | npx hardhat test 10 | GAS_REPORT=true npx hardhat test 11 | npx hardhat node 12 | npx hardhat run scripts/deploy.js 13 | ``` 14 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat") 2 | 3 | 4 | async function main() { 5 | const apeGoddessContractFactory = await ethers.getContractFactory("ApeGoddess") 6 | const apeGoddessContract = await apeGoddessContractFactory.deploy() 7 | await apeGoddessContract.deployed() 8 | 9 | console.log("ApeGoddess contract address is: ", apeGoddessContract.address) 10 | } 11 | 12 | main() 13 | .then(() => process.exit(0)) 14 | .catch((error) => { 15 | console.error(error) 16 | process.exit(1) 17 | }) -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox") 2 | require("dotenv").config({ path: ".env" }) 3 | 4 | const ALCHEMY_KEY = process.env.ALCHEMY_API_KEY 5 | const API_KEY = process.env.ETHERSCAN_API_KEY 6 | const PRIVATE_KEY = process.env.PRIVATE_KEY 7 | 8 | /** @type import('hardhat/config').HardhatUserConfig */ 9 | module.exports = { 10 | solidity: "0.8.9", 11 | networks: { 12 | rinkeby: { 13 | url: ALCHEMY_KEY, 14 | accounts: [PRIVATE_KEY] 15 | } 16 | }, 17 | etherscan: { 18 | apiKey: API_KEY 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ape-goddess", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "hardhat": "^2.11.1" 14 | }, 15 | "dependencies": { 16 | "@nomicfoundation/hardhat-toolbox": "^1.0.2", 17 | "@openzeppelin/contracts": "^4.7.3", 18 | "dotenv": "^16.0.2", 19 | "erc721a": "^4.2.2", 20 | "keccak256": "^1.0.6", 21 | "merkletreejs": "^0.2.32" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /merkle_tree/index.js: -------------------------------------------------------------------------------- 1 | const keccak256 = require("keccak256") 2 | const { MerkleTree } = require("merkletreejs") 3 | const { whitelistAddress } = require("./whitelistAddress.js") 4 | 5 | //creation of merkle tree from an array of addresses 6 | const whitelistAddressLeaves = whitelistAddress.map(x => keccak256(x)) 7 | const merkleTree = new MerkleTree(whitelistAddressLeaves, keccak256, { 8 | sortPairs: true 9 | }) 10 | 11 | const rootHash = merkleTree.getHexRoot() 12 | console.log("root is: ", rootHash) 13 | 14 | //generating merkle proof 15 | const leaf = keccak256("0x81dbE0486ce6274984dF3b575911757E5522dc7E") 16 | const proof = merkleTree.getHexProof(leaf) 17 | console.log("proof is: ", proof) -------------------------------------------------------------------------------- /contracts/ApeGoddess.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import "erc721a/contracts/ERC721A.sol"; 5 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | import "@openzeppelin/contracts/utils/Strings.sol"; 8 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 9 | 10 | contract ApeGoddess is ERC721A, Ownable, ReentrancyGuard { 11 | using Strings for uint256; 12 | 13 | bytes32 public merkleRoot; 14 | 15 | string public hiddenMetadataURI = "ipfs://Qmd6o1c8ap6i5d2ByYkMuCdHFs9qRKmzdTHuU73eHns5AL"; 16 | string public baseURI = ""; 17 | 18 | enum MintStatus { 19 | Not_Live, 20 | Whitelist_Mint, 21 | Public_Mint 22 | } 23 | MintStatus public mintStatus; 24 | 25 | uint256 public mintPrice = 0.01 ether; 26 | uint256 public maxSupply = 100; 27 | uint256 public maxPerWallet = 3; 28 | 29 | bool public revealed = false; 30 | 31 | mapping(address => bool) public whitelistClaimed; 32 | 33 | constructor() ERC721A("Ape Goddess", "AG") { 34 | mintStatus = MintStatus.Not_Live; 35 | } 36 | 37 | modifier mintCompliance(uint256 _quantity) { 38 | require(_quantity > 0 && (_quantity + _numberMinted(msg.sender) <= maxPerWallet), "Invalid Amount"); 39 | require(totalSupply() + _quantity <= maxSupply, "Sold Out"); 40 | require(msg.value >= (mintPrice * _quantity), "Incorrect Mint Price"); 41 | _; 42 | } 43 | 44 | function setBaseURI(string memory _baseURI) external onlyOwner { 45 | baseURI = _baseURI; 46 | } 47 | 48 | function tokenURI(uint256 _tokenId) public view virtual override returns(string memory) { 49 | require(_exists(_tokenId), "ERC721Metadata: URI query for nonexistent token"); 50 | 51 | string memory currentBaseURI = _baseURI(); 52 | 53 | if(revealed == false) { 54 | return hiddenMetadataURI; 55 | } else { 56 | return bytes(currentBaseURI).length > 0 57 | ? string(abi.encodePacked(currentBaseURI, _tokenId.toString(), ".json")) 58 | : ""; 59 | } 60 | } 61 | 62 | function whitelistMint(uint256 _quantity, bytes32[] calldata proof) external payable mintCompliance(_quantity) { 63 | require(mintStatus == MintStatus.Whitelist_Mint, "Whitelist mint not live"); 64 | require(!whitelistClaimed[msg.sender], "already claimed whitelist"); 65 | 66 | bytes32 leaf = keccak256(abi.encodePacked(msg.sender)); 67 | require(MerkleProof.verify(proof, merkleRoot, leaf)); 68 | 69 | if(_numberMinted(msg.sender) == maxPerWallet) { 70 | whitelistClaimed[msg.sender] = true; 71 | } 72 | 73 | _safeMint(msg.sender, _quantity); 74 | } 75 | 76 | function publicMint(uint256 _quantity) external payable mintCompliance(_quantity) { 77 | require(mintStatus == MintStatus.Public_Mint, "Public mint not Live"); 78 | _safeMint(msg.sender, _quantity); 79 | } 80 | 81 | function teamMint(uint256 _quantity) external payable onlyOwner{ 82 | require((_quantity + _numberMinted(owner())) <= 10, "Exceeded limit for owner"); 83 | _safeMint(owner(), _quantity); 84 | } 85 | 86 | function setHiddenMetadataURI(string memory _hiddenMetadataURI) external onlyOwner { 87 | hiddenMetadataURI = _hiddenMetadataURI; 88 | } 89 | 90 | function setRevealed() external onlyOwner{ 91 | revealed = !revealed; 92 | } 93 | 94 | function enableWhitelistMint() external onlyOwner { 95 | mintStatus = MintStatus.Whitelist_Mint; 96 | } 97 | 98 | function enablePublicMint() external onlyOwner { 99 | mintStatus = MintStatus.Public_Mint; 100 | } 101 | 102 | function _startTokenId() internal view virtual override returns(uint256) { 103 | return 1; 104 | } 105 | 106 | function setMerkleRoot(bytes32 _newMerkleRoot) external onlyOwner { 107 | merkleRoot = _newMerkleRoot; 108 | } 109 | 110 | function setMintPrice(uint256 _newMintPrice) external onlyOwner { 111 | mintPrice = _newMintPrice; 112 | } 113 | 114 | function setMaxSupply(uint256 _newMaxSupply) external onlyOwner { 115 | maxSupply = _newMaxSupply; 116 | } 117 | 118 | function withdrawETH() external onlyOwner nonReentrant { 119 | require(address(this).balance > 0, "Nothing to withdraw"); 120 | (bool sent, ) = payable(owner()).call{ value: address(this).balance }(""); 121 | require(sent, "Transaction failed"); 122 | } 123 | 124 | receive() external payable {} 125 | 126 | fallback() external payable {} 127 | } -------------------------------------------------------------------------------- /test/Lock.js: -------------------------------------------------------------------------------- 1 | const { 2 | time, 3 | loadFixture, 4 | } = require("@nomicfoundation/hardhat-network-helpers"); 5 | const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); 6 | const { expect } = require("chai"); 7 | 8 | describe("Lock", function () { 9 | // We define a fixture to reuse the same setup in every test. 10 | // We use loadFixture to run this setup once, snapshot that state, 11 | // and reset Hardhat Network to that snapshot in every test. 12 | async function deployOneYearLockFixture() { 13 | const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; 14 | const ONE_GWEI = 1_000_000_000; 15 | 16 | const lockedAmount = ONE_GWEI; 17 | const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; 18 | 19 | // Contracts are deployed using the first signer/account by default 20 | const [owner, otherAccount] = await ethers.getSigners(); 21 | 22 | const Lock = await ethers.getContractFactory("Lock"); 23 | const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); 24 | 25 | return { lock, unlockTime, lockedAmount, owner, otherAccount }; 26 | } 27 | 28 | describe("Deployment", function () { 29 | it("Should set the right unlockTime", async function () { 30 | const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); 31 | 32 | expect(await lock.unlockTime()).to.equal(unlockTime); 33 | }); 34 | 35 | it("Should set the right owner", async function () { 36 | const { lock, owner } = await loadFixture(deployOneYearLockFixture); 37 | 38 | expect(await lock.owner()).to.equal(owner.address); 39 | }); 40 | 41 | it("Should receive and store the funds to lock", async function () { 42 | const { lock, lockedAmount } = await loadFixture( 43 | deployOneYearLockFixture 44 | ); 45 | 46 | expect(await ethers.provider.getBalance(lock.address)).to.equal( 47 | lockedAmount 48 | ); 49 | }); 50 | 51 | it("Should fail if the unlockTime is not in the future", async function () { 52 | // We don't use the fixture here because we want a different deployment 53 | const latestTime = await time.latest(); 54 | const Lock = await ethers.getContractFactory("Lock"); 55 | await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( 56 | "Unlock time should be in the future" 57 | ); 58 | }); 59 | }); 60 | 61 | describe("Withdrawals", function () { 62 | describe("Validations", function () { 63 | it("Should revert with the right error if called too soon", async function () { 64 | const { lock } = await loadFixture(deployOneYearLockFixture); 65 | 66 | await expect(lock.withdraw()).to.be.revertedWith( 67 | "You can't withdraw yet" 68 | ); 69 | }); 70 | 71 | it("Should revert with the right error if called from another account", async function () { 72 | const { lock, unlockTime, otherAccount } = await loadFixture( 73 | deployOneYearLockFixture 74 | ); 75 | 76 | // We can increase the time in Hardhat Network 77 | await time.increaseTo(unlockTime); 78 | 79 | // We use lock.connect() to send a transaction from another account 80 | await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( 81 | "You aren't the owner" 82 | ); 83 | }); 84 | 85 | it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { 86 | const { lock, unlockTime } = await loadFixture( 87 | deployOneYearLockFixture 88 | ); 89 | 90 | // Transactions are sent using the first signer by default 91 | await time.increaseTo(unlockTime); 92 | 93 | await expect(lock.withdraw()).not.to.be.reverted; 94 | }); 95 | }); 96 | 97 | describe("Events", function () { 98 | it("Should emit an event on withdrawals", async function () { 99 | const { lock, unlockTime, lockedAmount } = await loadFixture( 100 | deployOneYearLockFixture 101 | ); 102 | 103 | await time.increaseTo(unlockTime); 104 | 105 | await expect(lock.withdraw()) 106 | .to.emit(lock, "Withdrawal") 107 | .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg 108 | }); 109 | }); 110 | 111 | describe("Transfers", function () { 112 | it("Should transfer the funds to the owner", async function () { 113 | const { lock, unlockTime, lockedAmount, owner } = await loadFixture( 114 | deployOneYearLockFixture 115 | ); 116 | 117 | await time.increaseTo(unlockTime); 118 | 119 | await expect(lock.withdraw()).to.changeEtherBalances( 120 | [owner, lock], 121 | [lockedAmount, -lockedAmount] 122 | ); 123 | }); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /test/ApeGoddess.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers } = require("hardhat"); 3 | const { MerkleTree } = require("merkletreejs"); 4 | const keccak256 = require("keccak256"); 5 | 6 | describe("ApeGoddess", function () { 7 | let ApeGoddess; 8 | let apeGoddess; 9 | let owner; 10 | let addr1; 11 | let addr2; 12 | let addrs; 13 | let merkleTree; 14 | let rootHash; 15 | 16 | const mintPrice = ethers.utils.parseEther("0.01"); 17 | const maxSupply = 100; 18 | const maxPerWallet = 3; 19 | const hiddenMetadataURI = "ipfs://Qmd6o1c8ap6i5d2ByYkMuCdHFs9qRKmzdTHuU73eHns5AL"; 20 | 21 | beforeEach(async function () { 22 | ApeGoddess = await ethers.getContractFactory("ApeGoddess"); 23 | [owner, addr1, addr2, ...addrs] = await ethers.getSigners(); 24 | apeGoddess = await ApeGoddess.deploy(); 25 | await apeGoddess.deployed(); 26 | 27 | // Setup Merkle Tree for whitelist 28 | const whitelistAddresses = [owner.address, addr1.address, addr2.address]; 29 | const leafNodes = whitelistAddresses.map(addr => keccak256(addr)); 30 | merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true }); 31 | rootHash = merkleTree.getHexRoot(); 32 | await apeGoddess.setMerkleRoot(rootHash); 33 | }); 34 | 35 | describe("Deployment", function () { 36 | it("Should set the correct name and symbol", async function () { 37 | expect(await apeGoddess.name()).to.equal("Ape Goddess"); 38 | expect(await apeGoddess.symbol()).to.equal("AG"); 39 | }); 40 | 41 | it("Should set the correct initial mint status", async function () { 42 | expect(await apeGoddess.mintStatus()).to.equal(0); // Not_Live 43 | }); 44 | 45 | it("Should set the correct initial hiddenMetadataURI", async function () { 46 | expect(await apeGoddess.hiddenMetadataURI()).to.equal(hiddenMetadataURI); 47 | }); 48 | 49 | it("Should set the correct initial mint price, max supply, and max per wallet", async function () { 50 | expect(await apeGoddess.mintPrice()).to.equal(mintPrice); 51 | expect(await apeGoddess.maxSupply()).to.equal(maxSupply); 52 | expect(await apeGoddess.maxPerWallet()).to.equal(maxPerWallet); 53 | }); 54 | 55 | it("Should set the correct owner", async function () { 56 | expect(await apeGoddess.owner()).to.equal(owner.address); 57 | }); 58 | }); 59 | 60 | describe("Minting", function () { 61 | it("Should allow owner to set mint price", async function () { 62 | const newMintPrice = ethers.utils.parseEther("0.02"); 63 | await apeGoddess.setMintPrice(newMintPrice); 64 | expect(await apeGoddess.mintPrice()).to.equal(newMintPrice); 65 | }); 66 | 67 | it("Should not allow non-owner to set mint price", async function () { 68 | const newMintPrice = ethers.utils.parseEther("0.02"); 69 | await expect(apeGoddess.connect(addr1).setMintPrice(newMintPrice)).to.be.revertedWith("Ownable: caller is not the owner"); 70 | }); 71 | 72 | it("Should allow owner to set max supply", async function () { 73 | const newMaxSupply = 200; 74 | await apeGoddess.setMaxSupply(newMaxSupply); 75 | expect(await apeGoddess.maxSupply()).to.equal(newMaxSupply); 76 | }); 77 | 78 | it("Should not allow non-owner to set max supply", async function () { 79 | const newMaxSupply = 200; 80 | await expect(apeGoddess.connect(addr1).setMaxSupply(newMaxSupply)).to.be.revertedWith("Ownable: caller is not the owner"); 81 | }); 82 | 83 | it("Should not allow minting when Not_Live", async function () { 84 | await expect(apeGoddess.whitelistMint(1, [])).to.be.revertedWith("Whitelist mint not live"); 85 | await expect(apeGoddess.publicMint(1)).to.be.revertedWith("Public mint not Live"); 86 | }); 87 | 88 | describe("Whitelist Mint", function () { 89 | beforeEach(async function () { 90 | await apeGoddess.enableWhitelistMint(); 91 | }); 92 | 93 | it("Should allow whitelisted user to mint", async function () { 94 | const leaf = keccak256(addr1.address); 95 | const proof = merkleTree.getHexProof(leaf); 96 | await apeGoddess.connect(addr1).whitelistMint(1, proof, { value: mintPrice }); 97 | expect(await apeGoddess.ownerOf(1)).to.equal(addr1.address); 98 | }); 99 | 100 | it("Should not allow non-whitelisted user to mint", async function () { 101 | const leaf = keccak256(addrs[0].address); 102 | const proof = merkleTree.getHexProof(leaf); 103 | await expect(apeGoddess.connect(addrs[0]).whitelistMint(1, proof, { value: mintPrice })).to.be.reverted; 104 | }); 105 | 106 | it("Should not allow whitelisted user to mint more than maxPerWallet", async function () { 107 | const leaf = keccak256(addr1.address); 108 | const proof = merkleTree.getHexProof(leaf); 109 | await apeGoddess.connect(addr1).whitelistMint(maxPerWallet, proof, { value: mintPrice.mul(maxPerWallet) }); 110 | await expect(apeGoddess.connect(addr1).whitelistMint(1, proof, { value: mintPrice })).to.be.revertedWith("Invalid Amount"); 111 | }); 112 | 113 | it("Should mark whitelist claimed after reaching maxPerWallet", async function () { 114 | const leaf = keccak256(addr1.address); 115 | const proof = merkleTree.getHexProof(leaf); 116 | await apeGoddess.connect(addr1).whitelistMint(maxPerWallet, proof, { value: mintPrice.mul(maxPerWallet) }); 117 | expect(await apeGoddess.whitelistClaimed(addr1.address)).to.be.true; 118 | }); 119 | }); 120 | 121 | describe("Public Mint", function () { 122 | beforeEach(async function () { 123 | await apeGoddess.enablePublicMint(); 124 | }); 125 | 126 | it("Should allow public user to mint", async function () { 127 | await apeGoddess.connect(addr1).publicMint(1, { value: mintPrice }); 128 | expect(await apeGoddess.ownerOf(1)).to.equal(addr1.address); 129 | }); 130 | 131 | it("Should not allow public user to mint more than maxPerWallet", async function () { 132 | await apeGoddess.connect(addr1).publicMint(maxPerWallet, { value: mintPrice.mul(maxPerWallet) }); 133 | await expect(apeGoddess.connect(addr1).publicMint(1, { value: mintPrice })).to.be.revertedWith("Invalid Amount"); 134 | }); 135 | }); 136 | 137 | it("Should allow owner to team mint", async function () { 138 | await apeGoddess.teamMint(5); 139 | expect(await apeGoddess.balanceOf(owner.address)).to.equal(5); 140 | }); 141 | 142 | it("Should not allow non-owner to team mint", async function () { 143 | await expect(apeGoddess.connect(addr1).teamMint(1)).to.be.revertedWith("Ownable: caller is not the owner"); 144 | }); 145 | 146 | it("Should not allow team mint more than 10 tokens", async function () { 147 | await apeGoddess.teamMint(10); 148 | await expect(apeGoddess.teamMint(1)).to.be.revertedWith("Exceeded limit for owner"); 149 | }); 150 | }); 151 | 152 | describe("Metadata", function () { 153 | it("Should return hiddenMetadataURI when not revealed", async function () { 154 | await apeGoddess.enablePublicMint(); 155 | await apeGoddess.publicMint(1, { value: mintPrice }); 156 | expect(await apeGoddess.tokenURI(1)).to.equal(hiddenMetadataURI); 157 | }); 158 | 159 | it("Should return baseURI + tokenId when revealed", async function () { 160 | const newBaseURI = "ipfs://newBaseURI/"; 161 | await apeGoddess.setBaseURI(newBaseURI); 162 | await apeGoddess.setRevealed(); 163 | await apeGoddess.enablePublicMint(); 164 | await apeGoddess.publicMint(1, { value: mintPrice }); 165 | expect(await apeGoddess.tokenURI(1)).to.equal("ipfs://newBaseURI/1.json"); 166 | }); 167 | 168 | it("Should allow owner to set hiddenMetadataURI", async function () { 169 | const newHiddenURI = "ipfs://newHiddenURI/"; 170 | await apeGoddess.setHiddenMetadataURI(newHiddenURI); 171 | expect(await apeGoddess.hiddenMetadataURI()).to.equal(newHiddenURI); 172 | }); 173 | 174 | it("Should not allow non-owner to set hiddenMetadataURI", async function () { 175 | const newHiddenURI = "ipfs://newHiddenURI/"; 176 | await expect(apeGoddess.connect(addr1).setHiddenMetadataURI(newHiddenURI)).to.be.revertedWith("Ownable: caller is not the owner"); 177 | }); 178 | 179 | it("Should allow owner to toggle revealed status", async function () { 180 | expect(await apeGoddess.revealed()).to.be.false; 181 | await apeGoddess.setRevealed(); 182 | expect(await apeGoddess.revealed()).to.be.true; 183 | await apeGoddess.setRevealed(); 184 | expect(await apeGoddess.revealed()).to.be.false; 185 | }); 186 | 187 | it("Should not allow non-owner to toggle revealed status", async function () { 188 | await expect(apeGoddess.connect(addr1).setRevealed()).to.be.revertedWith("Ownable: caller is not the owner"); 189 | }); 190 | }); 191 | 192 | describe("Withdrawals", function () { 193 | it("Should allow owner to withdraw ETH", async function () { 194 | await apeGoddess.enablePublicMint(); 195 | await apeGoddess.connect(addr1).publicMint(1, { value: mintPrice }); 196 | 197 | const contractBalance = await ethers.provider.getBalance(apeGoddess.address); 198 | const ownerBalanceBefore = await ethers.provider.getBalance(owner.address); 199 | 200 | await apeGoddess.withdrawETH(); 201 | 202 | const ownerBalanceAfter = await ethers.provider.getBalance(owner.address); 203 | expect(ownerBalanceAfter.sub(ownerBalanceBefore)).to.be.closeTo(contractBalance, ethers.utils.parseEther("0.0001")); // Allowing for gas costs 204 | }); 205 | 206 | it("Should not allow non-owner to withdraw ETH", async function () { 207 | await apeGoddess.enablePublicMint(); 208 | await apeGoddess.connect(addr1).publicMint(1, { value: mintPrice }); 209 | await expect(apeGoddess.connect(addr1).withdrawETH()).to.be.revertedWith("Ownable: caller is not the owner"); 210 | }); 211 | 212 | it("Should not allow withdrawal if contract balance is 0", async function () { 213 | await expect(apeGoddess.withdrawETH()).to.be.revertedWith("Nothing to withdraw"); 214 | }); 215 | }); 216 | 217 | describe("Mint Status", function () { 218 | it("Should allow owner to enable whitelist mint", async function () { 219 | await apeGoddess.enableWhitelistMint(); 220 | expect(await apeGoddess.mintStatus()).to.equal(1); // Whitelist_Mint 221 | }); 222 | 223 | it("Should not allow non-owner to enable whitelist mint", async function () { 224 | await expect(apeGoddess.connect(addr1).enableWhitelistMint()).to.be.revertedWith("Ownable: caller is not the owner"); 225 | }); 226 | 227 | it("Should allow owner to enable public mint", async function () { 228 | await apeGoddess.enablePublicMint(); 229 | expect(await apeGoddess.mintStatus()).to.equal(2); // Public_Mint 230 | }); 231 | 232 | it("Should not allow non-owner to enable public mint", async function () { 233 | await expect(apeGoddess.connect(addr1).enablePublicMint()).to.be.revertedWith("Ownable: caller is not the owner"); 234 | }); 235 | }); 236 | }); 237 | --------------------------------------------------------------------------------