diff --git a/README.md b/README.md index 6da4e77..abc7a88 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,12 @@ Demo NFT collection for learning purposes. -## OPenSea Testnet - 1. https://testnets.opensea.io/collection/squarenft-cd2xk3yh7v - 2. https://testnets.opensea.io/collection/squarenft-bndes4idpq +## OpenSea Testnet + +1. https://testnets.opensea.io/collection/squarenft-cd2xk3yh7v +2. https://testnets.opensea.io/collection/squarenft-bndes4idpq +3. https://testnets.opensea.io/collection/squarenft-lemz9demvy +4. https://testnets.opensea.io/collection/squarenft-1xkwn34mdz ## Square NFT diff --git a/contracts/EpicNFT.sol b/contracts/EpicNFT.sol index e7fe69a..6061d6b 100644 --- a/contracts/EpicNFT.sol +++ b/contracts/EpicNFT.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "hardhat/console.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; +import "../lib/Base64.sol"; contract EpicNFT is ERC721URIStorage { using Counters for Counters.Counter; @@ -13,14 +14,129 @@ contract EpicNFT is ERC721URIStorage { console.log("My first NFT contract! EPIC!!!"); } + string baseSvg1 = + ""; + + string[] colors = [ + "red", + "green", + "blue", + "white", + "yellow", + "cyan", + "pink", + "magenta", + "silver", + "gold" + ]; + + string[] levels = [ + "Epic", + "Legendary", + "Heroic", + "Cool", + "Fantastic", + "Terrible", + "Crazy", + "Wild", + "Terrifying", + "Spooky" + ]; + + string[] classes = [ + "Assassin", + "Cleric", + "Rogue", + "Ninja", + "Lord", + "Wizard", + "Warrior", + "Berserker", + "Necromander", + "Summoner", + "Bard", + "Lancer" + ]; + + string[] jobs = [ + "Soldier", + "Healer", + "Explorer", + "Merchant", + "Developer", + "BlackSmith", + "Hitman", + "Cook", + "Hunter", + "Sailor" + ]; + function mint() public { uint256 tokenID = _tokenIds.current(); - _safeMint(msg.sender, tokenID); - _setTokenURI( - tokenID, - "data:application/json;base64,ewogICAgIm5hbWUiOiAiRXBpY0xvcmRIYW1idXJnZXIiLAogICAgImRlc2NyaXB0aW9uIjogIkFuIE5GVCBmcm9tIHRoZSBoaWdobHkgYWNjbGFpbWVkIHNxdWFyZSBjb2xsZWN0aW9uIiwKICAgICJpbWFnZSI6ICJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSEJ5WlhObGNuWmxRWE53WldOMFVtRjBhVzg5SW5oTmFXNVpUV2x1SUcxbFpYUWlJSFpwWlhkQ2IzZzlJakFnTUNBek5UQWdNelV3SWo0TkNpQWdJQ0E4YzNSNWJHVStMbUpoYzJVZ2V5Qm1hV3hzT2lCM2FHbDBaVHNnWm05dWRDMW1ZVzFwYkhrNklITmxjbWxtT3lCbWIyNTBMWE5wZW1VNklERTBjSGc3SUgwOEwzTjBlV3hsUGcwS0lDQWdJRHh5WldOMElIZHBaSFJvUFNJeE1EQWxJaUJvWldsbmFIUTlJakV3TUNVaUlHWnBiR3c5SW1Kc1lXTnJJaUF2UGcwS0lDQWdJRHgwWlhoMElIZzlJalV3SlNJZ2VUMGlOVEFsSWlCamJHRnpjejBpWW1GelpTSWdaRzl0YVc1aGJuUXRZbUZ6Wld4cGJtVTlJbTFwWkdSc1pTSWdkR1Y0ZEMxaGJtTm9iM0k5SW0xcFpHUnNaU0krUlhCcFkweHZjbVJJWVcxaWRYSm5aWEk4TDNSbGVIUStEUW84TDNOMlp6ND0iCn0=" + + string memory color = pickRandomWord(colors, "color", tokenID); + string memory first = pickRandomWord(levels, "first", tokenID); + string memory second = pickRandomWord(classes, "second", tokenID); + string memory third = pickRandomWord(jobs, "third", tokenID); + string memory finalWord = string( + abi.encodePacked(color, first, second, third) ); + string memory finalSvg = string( + abi.encodePacked( + baseSvg1, + color, + baseSvg2, + first, + second, + third, + "" + ) + ); + + string memory json = Base64.encode( + bytes( + string( + abi.encodePacked( + '{"name": "', + finalWord, + '", "description": "A highly acclaimed collection of squares.", "image": "data:image/svg+xml;base64,', + Base64.encode(bytes(finalSvg)), + '"}' + ) + ) + ) + ); + + string memory tokenUri = string( + abi.encodePacked("data:application/json;base64,", json) + ); + + console.log("\n--------------------"); + console.log(tokenUri); + console.log("--------------------\n"); + + _safeMint(msg.sender, tokenID); + _setTokenURI(tokenID, tokenUri); console.log("NFT %s minted to %s", tokenID, msg.sender); _tokenIds.increment(); } + + function pickRandomWord( + string[] memory words, + string memory position, + uint256 tokenID + ) public pure returns (string memory) { + uint256 rand = random( + string(abi.encodePacked(position, Strings.toString(tokenID))) + ); + rand = rand % words.length; + return words[rand]; + } + + function random(string memory input) internal pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(input))); + } } diff --git a/lib/Base64.sol b/lib/Base64.sol new file mode 100644 index 0000000..3119f21 --- /dev/null +++ b/lib/Base64.sol @@ -0,0 +1,78 @@ +/** + *Submitted for verification at Etherscan.io on 2021-09-05 + */ + +// 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); + } +}