├── .gitignore ├── hardhat.config.js ├── README.md ├── scripts ├── run.js └── deploy.js ├── package.json ├── tokenURI.json └── contracts ├── libraries └── Base64.sol └── MyEpicNFT.sol /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | 7 | #Hardhat files 8 | cache 9 | artifacts 10 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-waffle'); 2 | require("dotenv").config({ path: ".env" }); 3 | 4 | module.exports = { 5 | solidity: '0.8.1', 6 | networks: { 7 | rinkeby: { 8 | url: process.env.ALCHEMY_API_KEY_URL, 9 | accounts: [process.env.RINKEBY_PRIVATE_KEY], 10 | }, 11 | }, 12 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basic Sample Hardhat Project 2 | 3 | This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, a sample script that deploys that contract, and an example of a task implementation, which simply lists the available accounts. 4 | 5 | Try running some of the following tasks: 6 | 7 | ```shell 8 | npx hardhat accounts 9 | npx hardhat compile 10 | npx hardhat clean 11 | npx hardhat test 12 | npx hardhat node 13 | node scripts/sample-script.js 14 | npx hardhat help 15 | ``` 16 | -------------------------------------------------------------------------------- /scripts/run.js: -------------------------------------------------------------------------------- 1 | const main = async () => { 2 | const nftContractFactory = await hre.ethers.getContractFactory('MyEpicNFT'); 3 | const nftContract = await nftContractFactory.deploy(); 4 | await nftContract.deployed(); 5 | console.log("Contract deployed to:", nftContract.address); 6 | 7 | // Call the function. 8 | let txn = await nftContract.makeAnEpicNFT() 9 | // Wait for it to be mined. 10 | await txn.wait() 11 | 12 | // Mint another NFT for fun. 13 | txn = await nftContract.makeAnEpicNFT() 14 | // Wait for it to be mined. 15 | await txn.wait() 16 | 17 | }; 18 | 19 | const runMain = async () => { 20 | try { 21 | await main(); 22 | process.exit(0); 23 | } catch (error) { 24 | console.log(error); 25 | process.exit(1); 26 | } 27 | }; 28 | 29 | runMain(); -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const main = async () => { 2 | const nftContractFactory = await hre.ethers.getContractFactory('MyEpicNFT'); 3 | const nftContract = await nftContractFactory.deploy(); 4 | await nftContract.deployed(); 5 | console.log("Contract deployed to:", nftContract.address); 6 | 7 | // Call the function. 8 | let txn = await nftContract.makeAnEpicNFT() 9 | // Wait for it to be mined. 10 | await txn.wait() 11 | console.log("Minted NFT #1") 12 | 13 | // txn = await nftContract.makeAnEpicNFT() 14 | // // Wait for it to be mined. 15 | // await txn.wait() 16 | // console.log("Minted NFT #2") 17 | }; 18 | 19 | const runMain = async () => { 20 | try { 21 | await main(); 22 | process.exit(0); 23 | } catch (error) { 24 | console.log(error); 25 | process.exit(1); 26 | } 27 | }; 28 | 29 | runMain(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "epic-nfts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/clever-web/epic-nfts.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/clever-web/epic-nfts/issues" 18 | }, 19 | "homepage": "https://github.com/clever-web/epic-nfts#readme", 20 | "devDependencies": { 21 | "@nomiclabs/hardhat-ethers": "^2.0.5", 22 | "@nomiclabs/hardhat-waffle": "^2.0.3", 23 | "chai": "^4.3.6", 24 | "ethereum-waffle": "^3.4.4", 25 | "ethers": "^5.6.5", 26 | "hardhat": "^2.9.3" 27 | }, 28 | "dependencies": { 29 | "@openzeppelin/contracts": "^4.6.0", 30 | "dotenv": "^16.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tokenURI.json: -------------------------------------------------------------------------------- 1 | 2 | The tokenURI is where the actual NFT data lives. 3 | And it usually links to a JSON file called the metadata that looks something like this: 4 | 5 | { 6 | "name": "Spongebob Cowboy Pants", 7 | "description": "A silent hero. A watchful protector.", 8 | "image": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCAzNTAgMzUwIj4KICAgIDxzdHlsZT4uYmFzZSB7IGZpbGw6IHdoaXRlOyBmb250LWZhbWlseTogc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsgfTwvc3R5bGU+CiAgICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJibGFjayIgLz4KICAgIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBjbGFzcz0iYmFzZSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RXBpY0xvcmRIYW1idXJnZXI8L3RleHQ+Cjwvc3ZnPg==" 9 | } 10 | 11 | { 12 | "name": "EpicLordHamburger", 13 | "description": "An NFT from the highly acclaimed square collection", 14 | "image": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCAzNTAgMzUwIj4KICAgIDxzdHlsZT4uYmFzZSB7IGZpbGw6IHdoaXRlOyBmb250LWZhbWlseTogc2VyaWY7IGZvbnQtc2l6ZTogMTRweDsgfTwvc3R5bGU+CiAgICA8cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJibGFjayIgLz4KICAgIDx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBjbGFzcz0iYmFzZSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RXBpY0xvcmRIYW1idXJnZXI8L3RleHQ+Cjwvc3ZnPg==" 15 | } 16 | 17 | data:image/svg+xml;base64,ewogICAgIm5hbWUiOiAiRXBpY0xvcmRIYW1idXJnZXIiLAogICAgImRlc2NyaXB0aW9uIjogIkFuIE5GVCBmcm9tIHRoZSBoaWdobHkgYWNjbGFpbWVkIHNxdWFyZSBjb2xsZWN0aW9uIiwKICAgICJpbWFnZSI6ICJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSEJ5WlhObGNuWmxRWE53WldOMFVtRjBhVzg5SW5oTmFXNVpUV2x1SUcxbFpYUWlJSFpwWlhkQ2IzZzlJakFnTUNBek5UQWdNelV3SWo0S0lDQWdJRHh6ZEhsc1pUNHVZbUZ6WlNCN0lHWnBiR3c2SUhkb2FYUmxPeUJtYjI1MExXWmhiV2xzZVRvZ2MyVnlhV1k3SUdadmJuUXRjMmw2WlRvZ01UUndlRHNnZlR3dmMzUjViR1UrQ2lBZ0lDQThjbVZqZENCM2FXUjBhRDBpTVRBd0pTSWdhR1ZwWjJoMFBTSXhNREFsSWlCbWFXeHNQU0ppYkdGamF5SWdMejRLSUNBZ0lEeDBaWGgwSUhnOUlqVXdKU0lnZVQwaU5UQWxJaUJqYkdGemN6MGlZbUZ6WlNJZ1pHOXRhVzVoYm5RdFltRnpaV3hwYm1VOUltMXBaR1JzWlNJZ2RHVjRkQzFoYm1Ob2IzSTlJbTFwWkdSc1pTSStSWEJwWTB4dmNtUklZVzFpZFhKblpYSThMM1JsZUhRK0Nqd3ZjM1puUGc9PSIKfQ== 18 | -------------------------------------------------------------------------------- /contracts/libraries/Base64.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2021-09-05 3 | */ 4 | 5 | // SPDX-License-Identifier: MIT 6 | 7 | pragma solidity ^0.8.0; 8 | 9 | /// [MIT License] 10 | /// @title Base64 11 | /// @notice Provides a function for encoding some bytes in base64 12 | /// @author Brecht Devos 13 | library Base64 { 14 | bytes internal constant TABLE = 15 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 16 | 17 | /// @notice Encodes some bytes to the base64 representation 18 | function encode(bytes memory data) internal pure returns (string memory) { 19 | uint256 len = data.length; 20 | if (len == 0) return ""; 21 | 22 | // multiply by 4/3 rounded up 23 | uint256 encodedLen = 4 * ((len + 2) / 3); 24 | 25 | // Add some extra buffer at the end 26 | bytes memory result = new bytes(encodedLen + 32); 27 | 28 | bytes memory table = TABLE; 29 | 30 | assembly { 31 | let tablePtr := add(table, 1) 32 | let resultPtr := add(result, 32) 33 | 34 | for { 35 | let i := 0 36 | } lt(i, len) { 37 | 38 | } { 39 | i := add(i, 3) 40 | let input := and(mload(add(data, i)), 0xffffff) 41 | 42 | let out := mload(add(tablePtr, and(shr(18, input), 0x3F))) 43 | out := shl(8, out) 44 | out := add( 45 | out, 46 | and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF) 47 | ) 48 | out := shl(8, out) 49 | out := add( 50 | out, 51 | and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF) 52 | ) 53 | out := shl(8, out) 54 | out := add( 55 | out, 56 | and(mload(add(tablePtr, and(input, 0x3F))), 0xFF) 57 | ) 58 | out := shl(224, out) 59 | 60 | mstore(resultPtr, out) 61 | 62 | resultPtr := add(resultPtr, 4) 63 | } 64 | 65 | switch mod(len, 3) 66 | case 1 { 67 | mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) 68 | } 69 | case 2 { 70 | mstore(sub(resultPtr, 1), shl(248, 0x3d)) 71 | } 72 | 73 | mstore(result, encodedLen) 74 | } 75 | 76 | return string(result); 77 | } 78 | } -------------------------------------------------------------------------------- /contracts/MyEpicNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.1; 4 | 5 | import "@openzeppelin/contracts/utils/Strings.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 7 | import "@openzeppelin/contracts/utils/Counters.sol"; 8 | import "hardhat/console.sol"; 9 | 10 | // We need to import the helper functions from the contract that we copy/pasted. 11 | import { Base64 } from "./libraries/Base64.sol"; 12 | 13 | contract MyEpicNFT is ERC721URIStorage { 14 | using Counters for Counters.Counter; 15 | Counters.Counter private _tokenIds; 16 | 17 | string baseSvg = ""; 18 | 19 | string[] firstWords = ["dog", "cat", "rabbit", "bear"]; 20 | string[] secondWords = ["rice", "cake", "bread", "sundae"]; 21 | string[] thirdWords = ["wine ", "cider", "coal", "juice"]; 22 | 23 | // MAGICAL EVENTS. 24 | event NewEpicNFTMinted(address sender, uint256 tokenId); 25 | 26 | constructor() ERC721 ("SquareNFT", "SQUARE") { 27 | console.log("This is my NFT contract. Woah!"); 28 | } 29 | 30 | function pickRandomFirstWord(uint256 tokenId) public view returns (string memory) { 31 | uint256 rand = random(string(abi.encodePacked("FIRST_WORD", Strings.toString(tokenId)))); 32 | rand = rand % firstWords.length; 33 | return firstWords[rand]; 34 | } 35 | 36 | function pickRandomSecondWord(uint256 tokenId) public view returns (string memory) { 37 | uint256 rand = random(string(abi.encodePacked("SECOND_WORD", Strings.toString(tokenId)))); 38 | rand = rand % secondWords.length; 39 | return secondWords[rand]; 40 | } 41 | 42 | function pickRandomThirdWord(uint256 tokenId) public view returns (string memory) { 43 | uint256 rand = random(string(abi.encodePacked("THIRD_WORD", Strings.toString(tokenId)))); 44 | rand = rand % thirdWords.length; 45 | return thirdWords[rand]; 46 | } 47 | 48 | function random(string memory input) internal pure returns (uint256) { 49 | return uint256(keccak256(abi.encodePacked(input))); 50 | } 51 | 52 | function makeAnEpicNFT() public { 53 | uint256 newItemId = _tokenIds.current(); 54 | 55 | string memory first = pickRandomFirstWord(newItemId); 56 | string memory second = pickRandomSecondWord(newItemId); 57 | string memory third = pickRandomThirdWord(newItemId); 58 | string memory combinedWord = string(abi.encodePacked(first, second, third)); 59 | 60 | string memory finalSvg = string(abi.encodePacked(baseSvg, combinedWord, "")); 61 | 62 | // Get all the JSON metadata in place and base64 encode it. 63 | string memory json = Base64.encode( 64 | bytes( 65 | string( 66 | abi.encodePacked( 67 | '{"name": "', 68 | // We set the title of our NFT as the generated word. 69 | combinedWord, 70 | '", "description": "A highly acclaimed collection of squares.", "image": "data:image/svg+xml;base64,', 71 | // We add data:image/svg+xml;base64 and then append our base64 encode our svg. 72 | Base64.encode(bytes(finalSvg)), 73 | '"}' 74 | ) 75 | ) 76 | ) 77 | ); 78 | 79 | // Just like before, we prepend data:application/json;base64, to our data. 80 | string memory finalTokenUri = string( 81 | abi.encodePacked("data:application/json;base64,", json) 82 | ); 83 | 84 | console.log("\n--------------------"); 85 | console.log(finalTokenUri); 86 | console.log("--------------------\n"); 87 | 88 | _safeMint(msg.sender, newItemId); 89 | 90 | // Update your URI!!! 91 | _setTokenURI(newItemId, finalTokenUri); 92 | 93 | _tokenIds.increment(); 94 | console.log("An NFT w/ ID %s has been minted to %s", newItemId, msg.sender); 95 | 96 | // EMIT MAGICAL EVENTS. 97 | emit NewEpicNFTMinted(msg.sender, newItemId); 98 | } 99 | } --------------------------------------------------------------------------------