MCPcopy Index your code
hub / github.com/LearnWeb3DAO/NFT-Collection

github.com/LearnWeb3DAO/NFT-Collection @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
16 symbols 36 edges 11 files 0 documented · 0% updated 3y ago★ 893 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

NFT-Collection

Now its time for you to launch your own NFT collection - Crypto Devs.

Requirements

  • There should only exist 20 Crypto Dev NFT's and each one of them should be unique.
  • User's should be able to mint only 1 NFT with one transaction.
  • Whitelisted users, should have a 5 min presale period before the actual sale where they are guaranteed 1 NFT per transaction.
  • There should be a website for your NFT Collection.

Lets start building 🚀

Prerequisites

Theory

  • What is a Non-Fungible Token? Fungible means to be the same or interchangeable eg Eth is fungible. With this in mind, NFTs are unique; each one is different. Every single token has unique characteristics and values. They are all distinguishable from one another and are not interchangeable eg Unique Art

  • What is ERC-721? ERC-721 is an open standard that describes how to build Non-Fungible tokens on EVM (Ethereum Virtual Machine) compatible blockchains; it is a standard interface for Non-Fungible tokens; it has a set of rules which make it easy to work with NFTs. Before moving ahead have a look at all the functions supported by ERC721

Build

Prefer a Video?

If you would rather learn from a video, we have a recording available of this tutorial on our YouTube. Watch the video by clicking on the screenshot below, or go ahead and read the tutorial! NFT-Collection Tutorial Part-1 NFT-Collection Tutorial Part-2

Smart Contract

  • We would also be using Ownable.sol from Openzeppelin which helps you manage the Ownership of a contract

  • By default, the owner of an Ownable contract is the account that deployed it, which is usually exactly what you want.

  • Ownable also lets you:

    • transferOwnership from the owner account to a new one, and
    • renounceOwnership for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over.
  • We would also be using an extension of ERC721 known as ERC721 Enumerable

  • ERC721 Enumerable helps you to keep track of all the tokenIds in the contract and also the tokensIds held by an address for a given contract.
  • Please have a look at the functions it implements before moving ahead

To build the smart contract we would be using Hardhat. Hardhat is an Ethereum development environment and framework designed for full stack development in Solidity. In simple words you can write your smart contract, deploy them, run tests, and debug your code.

  • To setup a Hardhat project, Open up a terminal and execute these commands

bash mkdir NFT-Collection cd NFT-Collection mkdir hardhat-tutorial cd hardhat-tutorial npm init --yes npm install --save-dev hardhat - In the same directory where you installed Hardhat run:

bash npx hardhat

  • Select Create a Javascript project
  • Press enter for the already specified Hardhat Project root
  • Press enter for the question on if you want to add a .gitignore
  • Press enter for Do you want to install this sample project's dependencies with npm (@nomicfoundation/hardhat-toolbox)?

Now you have a hardhat project ready to go!

If you are on Windows, please do this extra step and install these libraries as well :)

npm install --save-dev @nomicfoundation/hardhat-toolbox

and press Enter for all the questions.

bash npm install @openzeppelin/contracts

  • We will need to call the Whitelist Contract that you deployed for your previous level to check for addresses that were whitelisted and give them presale access. As we only need to call mapping(address => bool) public whitelistedAddresses; We can create an interface for Whitelist contract with a function only for this mapping, this way we would save gas as we would not need to inherit and deploy the entire Whitelist Contract but only a part of it.

  • Create a new file inside the contracts directory and call it IWhitelist.sol

```go // SPDX-License-Identifier: MIT pragma solidity ^0.8.4;

  interface IWhitelist {
      function whitelistedAddresses(address) external view returns (bool);
  }

```

  • Now lets create a new file inside the contracts directory and call it CryptoDevs.sol

```go // SPDX-License-Identifier: MIT pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./IWhitelist.sol";

contract CryptoDevs is ERC721Enumerable, Ownable {
    /**
     * @dev _baseTokenURI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`.
     */
    string _baseTokenURI;

    //  _price is the price of one Crypto Dev NFT
    uint256 public _price = 0.01 ether;

    // _paused is used to pause the contract in case of an emergency
    bool public _paused;

    // max number of CryptoDevs
    uint256 public maxTokenIds = 20;

    // total number of tokenIds minted
    uint256 public tokenIds;

    // Whitelist contract instance
    IWhitelist whitelist;

    // boolean to keep track of whether presale started or not
    bool public presaleStarted;

    // timestamp for when presale would end
    uint256 public presaleEnded;

    modifier onlyWhenNotPaused {
        require(!_paused, "Contract currently paused");
        _;
    }

    /**
     * @dev ERC721 constructor takes in a `name` and a `symbol` to the token collection.
     * name in our case is `Crypto Devs` and symbol is `CD`.
     * Constructor for Crypto Devs takes in the baseURI to set _baseTokenURI for the collection.
     * It also initializes an instance of whitelist interface.
     */
    constructor (string memory baseURI, address whitelistContract) ERC721("Crypto Devs", "CD") {
        _baseTokenURI = baseURI;
        whitelist = IWhitelist(whitelistContract);
    }

    /**
    * @dev startPresale starts a presale for the whitelisted addresses
     */
    function startPresale() public onlyOwner {
        presaleStarted = true;
        // Set presaleEnded time as current timestamp + 5 minutes
        // Solidity has cool syntax for timestamps (seconds, minutes, hours, days, years)
        presaleEnded = block.timestamp + 5 minutes;
    }

    /**
     * @dev presaleMint allows a user to mint one NFT per transaction during the presale.
     */
    function presaleMint() public payable onlyWhenNotPaused {
        require(presaleStarted && block.timestamp < presaleEnded, "Presale is not running");
        require(whitelist.whitelistedAddresses(msg.sender), "You are not whitelisted");
        require(tokenIds < maxTokenIds, "Exceeded maximum Crypto Devs supply");
        require(msg.value >= _price, "Ether sent is not correct");
        tokenIds += 1;
        //_safeMint is a safer version of the _mint function as it ensures that
        // if the address being minted to is a contract, then it knows how to deal with ERC721 tokens
        // If the address being minted to is not a contract, it works the same way as _mint
        _safeMint(msg.sender, tokenIds);
    }

    /**
    * @dev mint allows a user to mint 1 NFT per transaction after the presale has ended.
    */
    function mint() public payable onlyWhenNotPaused {
        require(presaleStarted && block.timestamp >=  presaleEnded, "Presale has not ended yet");
        require(tokenIds < maxTokenIds, "Exceed maximum Crypto Devs supply");
        require(msg.value >= _price, "Ether sent is not correct");
        tokenIds += 1;
        _safeMint(msg.sender, tokenIds);
    }

    /**
    * @dev _baseURI overides the Openzeppelin's ERC721 implementation which by default
    * returned an empty string for the baseURI
    */
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;
    }

    /**
    * @dev setPaused makes the contract paused or unpaused
     */
    function setPaused(bool val) public onlyOwner {
        _paused = val;
    }

    /**
    * @dev withdraw sends all the ether in the contract
    * to the owner of the contract
     */
    function withdraw() public onlyOwner  {
        address _owner = owner();
        uint256 amount = address(this).balance;
        (bool sent, ) =  _owner.call{value: amount}("");
        require(sent, "Failed to send Ether");
    }

     // Function to receive Ether. msg.data must be empty
    receive() external payable {}

    // Fallback function is called when msg.data is not empty
    fallback() external payable {}
}

`` - Now we would installdotenvpackage to be able to import the env file and use it in our config. Open up a terminal pointing athardhat-tutorial` directory and execute this command

bash npm install dotenv

  • Now create a .env file in the hardhat-tutorial folder and add the following lines, use the instructions in the comments to get your Alchemy API Key URL and RINKEBY Private Key. Make sure that the account from which you get your rinkeby private key is funded with Rinkeby Ether.

```bash // Go to https://www.alchemyapi.io, sign up, create // a new App in its dashboard and select the network as Rinkeby, and replace "add-the-alchemy-key-url-here" with its key url ALCHEMY_API_KEY_URL="add-the-alchemy-key-url-here"

// Replace this private key with your RINKEBY account private key // To export your private key from Metamask, open Metamask and // go to Account Details > Export Private Key // Be aware of NEVER putting real Ether into testing accounts RINKEBY_PRIVATE_KEY="add-the-rinkeby-private-key-here" ```

  • Lets deploy the contract to rinkeby network. Create a new file, or replace the default file, named deploy.js under the scripts folder

  • Now we would write some code to deploy the contract in deploy.js file.

```js const { ethers } = require("hardhat"); require("dotenv").config({ path: ".env" }); const { WHITELIST_CONTRACT_ADDRESS, METADATA_URL } = require("../constants");

async function main() { // Address of the whitelist contract that you deployed in the previous module const whitelistContract = WHITELIST_CONTRACT_ADDRESS; // URL from where we can extract the metadata for a Crypto Dev NFT const metadataURL = METADATA_URL; / A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts, so cryptoDevsContract here is a factory for instances of our CryptoDevs contract. / const cryptoDevsContract = await ethers.getContractFactory("CryptoDevs");

// deploy the contract
const deployedCryptoDevsContract = await cryptoDevsContract.deploy(
  metadataURL,
  whitelistContract
);

// print the address of the deployed contract
console.log(
  "Crypto Devs Contract Address:",
  deployedCryptoDevsContract.address
);

}

// Call the main function and catch if there is any error main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); ```

  • As you can read, deploy.js requires some constants. Lets create a folder named constants under the hardhat-tutorial folder
  • Now add an index.js file inside the constants folder and add the following lines to the file. Replace "address-of-the-whitelist-contract" with the address of the whitelist contract that you deployed in the previous tutorial. For Metadata_URL, just copy the sample one that has been provided. We would replace this further down in the tutorial.

```js // Address of the Whitelist Contract that you deployed const WHITELIST_CONTRACT_ADDRESS = "address-of-the-whitelist-contract"; // URL to extract Metadata for a Crypto Dev NFT const METADATA_URL = "https://nft-collection-sneh1999.vercel.app/api/";

module.exports = { WHITELIST_CONTRACT_ADDRESS, METADATA_URL }; ```

  • Now open the hardhat.config.js file, we would add the rinkeby network here so that we can deploy our contract to rinkeby. Replace all the lines in the hardhat.config.js file with the given below lines

```js require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config({ path: ".env" });

const ALCHEMY_API_KEY_URL = process.env.ALCHEMY_API_KEY_URL;

const RINKEBY_PRIVATE_KEY = process.env.RINKEBY_PRIVATE_KEY;

module.exports = { solidity: "0.8.4", networks: { rinkeby: { url: ALCHEMY_API_KEY_URL, accounts: [RINKEBY_PRIVATE_KEY], }, }, }; ```

  • Compile the contract, open up a terminal pointing at `hardhat-tutor

Core symbols most depended-on inside this repo

getProviderOrSigner
called by 9
my-app/pages/index.js
checkIfPresaleStarted
called by 3
my-app/pages/index.js
checkIfPresaleEnded
called by 2
my-app/pages/index.js
getTokenIdsMinted
called by 2
my-app/pages/index.js
main
called by 1
hardhat-tutorial/scripts/sample-script.js
main
called by 1
hardhat-tutorial/scripts/deploy.js
connectWallet
called by 1
my-app/pages/index.js
getOwner
called by 1
my-app/pages/index.js

Shape

Function 16

Languages

TypeScript100%

Modules by API surface

my-app/pages/index.js11 symbols
my-app/pages/api/hello.js1 symbols
my-app/pages/api/[tokenId].js1 symbols
my-app/pages/_app.js1 symbols
hardhat-tutorial/scripts/sample-script.js1 symbols
hardhat-tutorial/scripts/deploy.js1 symbols

For agents

$ claude mcp add NFT-Collection \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact