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

Lets start building 🚀
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
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!

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:
We would also be using an extension of ERC721 known as ERC721 Enumerable
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.
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
Create a Javascript projectHardhat Project root.gitignoreDo 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.
@openzeppelin/contracts as we would be importing Openzeppelin's ERC721Enumerable Contract in our CryptoDevs contract.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);
}
```
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
.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); }); ```
deploy.js requires some constants. Lets create a folder named constants under the hardhat-tutorial folderindex.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 }; ```
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], }, }, }; ```
$ claude mcp add NFT-Collection \
-- python -m otcore.mcp_server <graph>