Skip to content

Commit

Permalink
dynamically generating svg on-chain
Browse files Browse the repository at this point in the history
  • Loading branch information
maxslimb committed Jun 14, 2022
1 parent d1bab96 commit 8742d2e
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 2 deletions.
80 changes: 78 additions & 2 deletions contracts/MyEpicNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -18,16 +19,91 @@ contract MyEpicNFT is ERC721URIStorage {
console.log("This is my NFT contract. Woah!");
}

string baseSvg = "<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMinYMin meet' viewBox='0 0 350 350'><style>.base { fill: white; font-family: serif; font-size: 24px; }</style><rect width='100%' height='100%' fill='black' /><text x='50%' y='50%' class='base' dominant-baseline='middle' text-anchor='middle'>";

// 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, "</text></svg>"));

// 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.
Expand Down
74 changes: 74 additions & 0 deletions contracts/libraries/Base64.sol
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
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);
}
}
2 changes: 2 additions & 0 deletions scripts/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const main = async () => {
// Wait for it to be mined.
await txn.wait()



};

const runMain = async () => {
Expand Down

0 comments on commit 8742d2e

Please sign in to comment.