Counting dNFT
View the template here.
This repository houses an example that automates counting with a dynamic SVG. The main contract is counting-svg.sol
which is automated to create an entirely onchain SVG.
// SPDX-License-Identifier: MIT
// An example of a consumer contract that relies on a subscription for funding.
pragma solidity 0.8.17;
// Imports
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
contract CountSVG is ERC721, ERC721URIStorage, Ownable {
// Setup counters, we use the OpenZeppelin Counters library
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
// Initialize the counter to 0
uint256 count = 0;
// Constructor for the contract
constructor() ERC721("Counting SVG", "cSVG") {}
// Mint a new NFT and update the URI
function safeMint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
updateURI();
}
// Update the URI for the NFT
function updateURI() internal {
// Build the SVG
string memory finalSVG = buildSVG();
//Encode the SVG and add the metadata
string memory json = Base64.encode(
bytes(
string(
abi.encodePacked(
'{"name": "Counting SVG",',
'"description": "An Automated Counting SVG",',
'"image": "data:image/svg+xml;base64,',
Base64.encode(bytes(finalSVG)),
'"}'
)
)
)
);
// Set the URI string
string memory finalTokenURI = string(abi.encodePacked("data:application/json;base64,", json));
// Update the URI
// NOTE: This is hardcoded to the first SVG only
_setTokenURI(0, finalTokenURI);
}
// Create the SVG
function buildSVG() internal view returns (string memory) {
string
memory headSVG = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='500' height='500' preserveAspectRatio='none' viewBox='0 0 500 500'> <g clip-path='url("#SvgjsClipPath1094")' fill='none'> <rect width='500' height='500' x='0' y='0' fill='#32325d'></rect> <circle r='23.56' cx='117.85' cy='66.24' fill='url(#SvgjsLinearGradient1095)'></circle> <circle r='23.145' cx='233.4' cy='16.22' fill='url(#SvgjsLinearGradient1096)'></circle> <circle r='16.155' cx='57.91' cy='279.29' fill='url(#SvgjsLinearGradient1097)'></circle> <circle r='29.12' cx='175.64' cy='2.15' fill='url(#SvgjsLinearGradient1098)'></circle> <circle r='33.07' cx='423.83' cy='387.89' fill='url(#SvgjsLinearGradient1099)'></circle> <circle r='33.35' cx='296.87' cy='307.98' fill='url(#SvgjsLinearGradient1100)'></circle> <circle r='31.39' cx='273.7' cy='61.31' fill='url(#SvgjsLinearGradient1101)'></circle> <circle r='48.695' cx='108.9' cy='421.22' fill='url(#SvgjsLinearGradient1102)'></circle> </g> <defs> <clipPath id='SvgjsClipPath1094'> <rect width='500' height='500' x='0' y='0'></rect> </clipPath> <linearGradient x1='70.72999999999999' y1='66.24' x2='164.96999999999997' y2='66.24' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1095'> <stop stop-color='#e298de' offset='0.1'></stop> <stop stop-color='#484687' offset='0.9'></stop> </linearGradient> <linearGradient x1='187.11' y1='16.22' x2='279.69' y2='16.22' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1096'> <stop stop-color='#32325d' offset='0.1'></stop> <stop stop-color='#424488' offset='0.9'></stop> </linearGradient> <linearGradient x1='25.599999999999994' y1='279.29' x2='90.22' y2='279.29' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1097'> <stop stop-color='#32325d' offset='0.1'></stop> <stop stop-color='#424488' offset='0.9'></stop> </linearGradient> <linearGradient x1='117.39999999999998' y1='2.1499999999999986' x2='233.88' y2='2.1499999999999986' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1098'> <stop stop-color='#e298de' offset='0.1'></stop> <stop stop-color='#484687' offset='0.9'></stop> </linearGradient> <linearGradient x1='357.69' y1='387.89' x2='489.97' y2='387.89' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1099'> <stop stop-color='#32325d' offset='0.1'></stop> <stop stop-color='#424488' offset='0.9'></stop> </linearGradient> <linearGradient x1='230.17000000000002' y1='307.98' x2='363.57000000000005' y2='307.98' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1100'> <stop stop-color='#84b6e0' offset='0.1'></stop> <stop stop-color='#464a8f' offset='0.9'></stop> </linearGradient> <linearGradient x1='210.92' y1='61.31' x2='336.48' y2='61.31' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1101'> <stop stop-color='#32325d' offset='0.1'></stop> <stop stop-color='#424488' offset='0.9'></stop> </linearGradient> <linearGradient x1='11.510000000000005' y1='421.22' x2='206.29000000000002' y2='421.22' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1102'> <stop stop-color='#84b6e0' offset='0.1'></stop> <stop stop-color='#464a8f' offset='0.9'></stop> </linearGradient> </defs>";
string memory tailSVG = "</svg>";
string memory bodySVG = string(
abi.encodePacked(
"<text x='50%' y='50%' fill='white' font-size='128' dominant-baseline='middle' text-anchor='middle'>",
Strings.toString(count),
"</text>"
)
);
// Concatenate the SVG parts
string memory _finalSVG = string(abi.encodePacked(headSVG, bodySVG, tailSVG));
return _finalSVG;
}
// Increment the counter
function addToCount() public {
count = count + 1;
updateURI();
}
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
return super.tokenURI(tokenId);
}
}