diff --git a/contracts/MyEpicNFT.sol b/contracts/MyEpicNFT.sol index 09ca0d0..794c215 100644 --- a/contracts/MyEpicNFT.sol +++ b/contracts/MyEpicNFT.sol @@ -2,10 +2,11 @@ pragma solidity ^0.8.1; // We first import some OpenZeppelin Contracts. +import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "hardhat/console.sol"; - +import { Base64 } from "./libraries/Base64.sol"; // We inherit the contract we imported. This means we'll have access // to the inherited contract's methods. contract MyEpicNFT is ERC721URIStorage { @@ -18,16 +19,91 @@ contract MyEpicNFT is ERC721URIStorage { console.log("This is my NFT contract. Woah!"); } +string baseSvg = ""; + + // I create three arrays, each with their own theme of random words. + // Pick some random funny words, names of anime characters, foods you like, whatever! + string[] firstWords = ["Aletta", "Vi", "Jack", "Sun", "Yort", "Bon","gui","Pui","Fanta","vim","kiask","James","johny","elena","eli","Vicram","Paul","Patricia","Kevin","Brian","george","Peter","sara","rachel"]; + string[] secondWords = ["Panipuri", "palak", "paneer", "sandwich", "burger", "cupcake","lime","maggie","coldice","pizza","donuts","muffin","high","pasta","pancakes","Pie","banana","bagel","Cheetos","Kiwi"]; + string[] thirdWords = ["jurrasic", "ghostbusters", "kgf", "gold", "silver", "bronze","kingkong","sonic","housefull","dhamal","dhol","prithviraj","smarat","shivaji","jodha","padmavat","akbar","Coolie","shandaar","adam","Avatar"]; + + +function pickRandomFirstWord(uint256 tokenId) public view returns (string memory) { + // I seed the random generator. More on this in the lesson. + uint256 rand = random(string(abi.encodePacked("FIRST_WORD", Strings.toString(tokenId)))); + // Squash the # between 0 and the length of the array to avoid going out of bounds. + rand = rand % firstWords.length; + return firstWords[rand]; + } + + function pickRandomSecondWord(uint256 tokenId) public view returns (string memory) { + uint256 rand = random(string(abi.encodePacked("SECOND_WORD", Strings.toString(tokenId)))); + rand = rand % secondWords.length; + return secondWords[rand]; + } + + function pickRandomThirdWord(uint256 tokenId) public view returns (string memory) { + uint256 rand = random(string(abi.encodePacked("THIRD_WORD", Strings.toString(tokenId)))); + rand = rand % thirdWords.length; + return thirdWords[rand]; + } + + function random(string memory input) internal pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(input))); + } + + // A function our user will hit to get their NFT. function makeAnEpicNFT() public { // Get the current tokenId, this starts at 0. uint256 newItemId = _tokenIds.current(); + string memory first = pickRandomFirstWord(newItemId); + string memory second = pickRandomSecondWord(newItemId); + string memory third = pickRandomThirdWord(newItemId); + + string memory combinedWord = string(abi.encodePacked(first, second, third)); + + string memory finalSvg = string(abi.encodePacked(baseSvg, combinedWord, "")); + + // Get all the JSON metadata in place and base64 encode it. + string memory json = Base64.encode( + bytes( + string( + abi.encodePacked( + '{"name": "', + // We set the title of our NFT as the generated word. + combinedWord, + '", "description": "A highly acclaimed collection of art.", "image": "data:image/svg+xml;base64,', + // We add data:image/svg+xml;base64 and then append our base64 encode our svg. + Base64.encode(bytes(finalSvg)), + '"}' + ) + ) + ) + ); + + // Just like before, we prepend data:application/json;base64, to our data. + string memory finalTokenUri = string( + abi.encodePacked("data:application/json;base64,", json) + ); + + console.log("\n--------------------"); + console.log( + string( + abi.encodePacked( + "https://nftpreview.0xdev.codes/?code=", + finalTokenUri + ) + ) + ); + console.log("--------------------\n"); + // Actually mint the NFT to the sender using msg.sender. _safeMint(msg.sender, newItemId); // Set the NFTs data. - _setTokenURI(newItemId, "data:application/json;base64,eyJuYW1lIjoiQ2xhc3Mgb2YgMjAyMiIsImRlc2NyaXB0aW9uIjoiVG8gdGhlIEdyYWR1YXRpbmcgY2xhc3Mgb2YgMjAyMiIsImltYWdlIjoiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhCeVpYTmxjblpsUVhOd1pXTjBVbUYwYVc4OUluaE5hVzVaVFdsdUlHMWxaWFFpSUhacFpYZENiM2c5SWpBZ01DQXpOVEFnTXpVd0lqNEtJQ0FnSUR4emRIbHNaVDR1WW1GelpTQjdJR1pwYkd3NklIZG9hWFJsT3lCbWIyNTBMV1poYldsc2VUb2djMlZ5YVdZN0lHWnZiblF0YzJsNlpUb2dNVFJ3ZURzZ2ZUd3ZjM1I1YkdVK0NpQWdJQ0E4Y21WamRDQjNhV1IwYUQwaU1UQXdKU0lnYUdWcFoyaDBQU0l4TURBbElpQm1hV3hzUFNKaWJHRmpheUlnTHo0S0lDQWdJRHgwWlhoMElIZzlJalV3SlNJZ2VUMGlOVEFsSWlCamJHRnpjejBpWW1GelpTSWdaRzl0YVc1aGJuUXRZbUZ6Wld4cGJtVTlJbTFwWkdSc1pTSWdkR1Y0ZEMxaGJtTm9iM0k5SW0xcFpHUnNaU0krUlhCcFkweHZjbVJJWVcxaWRYSm5aWEk4TDNSbGVIUStDand2YzNablBnPT0ifQ=="); + _setTokenURI(newItemId, finalTokenUri); console.log("An NFT w/ ID %s has been minted to %s", newItemId, msg.sender); // Increment the counter for when the next NFT is minted. diff --git a/contracts/libraries/Base64.sol b/contracts/libraries/Base64.sol new file mode 100644 index 0000000..c5bb843 --- /dev/null +++ b/contracts/libraries/Base64.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// [MIT License] +/// @title Base64 +/// @notice Provides a function for encoding some bytes in base64 +/// @author Brecht Devos +library Base64 { + bytes internal constant TABLE = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /// @notice Encodes some bytes to the base64 representation + function encode(bytes memory data) internal pure returns (string memory) { + uint256 len = data.length; + if (len == 0) return ""; + + // multiply by 4/3 rounded up + uint256 encodedLen = 4 * ((len + 2) / 3); + + // Add some extra buffer at the end + bytes memory result = new bytes(encodedLen + 32); + + bytes memory table = TABLE; + + assembly { + let tablePtr := add(table, 1) + let resultPtr := add(result, 32) + + for { + let i := 0 + } lt(i, len) { + + } { + i := add(i, 3) + let input := and(mload(add(data, i)), 0xffffff) + + let out := mload(add(tablePtr, and(shr(18, input), 0x3F))) + out := shl(8, out) + out := add( + out, + and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF) + ) + out := shl(8, out) + out := add( + out, + and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF) + ) + out := shl(8, out) + out := add( + out, + and(mload(add(tablePtr, and(input, 0x3F))), 0xFF) + ) + out := shl(224, out) + + mstore(resultPtr, out) + + resultPtr := add(resultPtr, 4) + } + + switch mod(len, 3) + case 1 { + mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) + } + case 2 { + mstore(sub(resultPtr, 1), shl(248, 0x3d)) + } + + mstore(result, encodedLen) + } + + return string(result); + } +} \ No newline at end of file diff --git a/scripts/run.js b/scripts/run.js index aef5758..103b2a3 100644 --- a/scripts/run.js +++ b/scripts/run.js @@ -14,6 +14,8 @@ const main = async () => { // Wait for it to be mined. await txn.wait() + + }; const runMain = async () => {