diff --git a/docs/Contracts/SvgnftContract.sol b/docs/Contracts/SvgnftContract.sol new file mode 100644 index 0000000..4b4ea73 --- /dev/null +++ b/docs/Contracts/SvgnftContract.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.8; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "base64-sol/base64.sol"; +import "hardhat/console.sol"; + +error ERC721Metadata__URI_QueryFor_NonExistentToken(); + +contract SvgNft is ERC721, Ownable { + uint256 private s_tokenCounter; + string private s_ImageURI; + + mapping(uint256 => int256) private s_tokenIdToHighValues; + event CreatedNFT(uint256 indexed tokenId); + + constructor(string memory Svg) ERC721("Dynamic SVG NFT", "DSN") { + s_tokenCounter = 0; + s_ImageURI = svgToImageURI(Svg); + } + + function safeMintNFT() public { + _safeMint(msg.sender, s_tokenCounter); + s_tokenCounter = s_tokenCounter + 1; + emit CreatedNFT(s_tokenCounter, highValue); + } + + // You could also just upload the raw SVG and have solildity convert it! + function svgToImageURI( + string memory svg + ) public pure returns (string memory) { + // example: + // '' + // would return "" + string memory baseURL = "data:image/svg+xml;base64,"; + string memory svgBase64Encoded = Base64.encode( + bytes(string(abi.encodePacked(svg))) + ); + return string(abi.encodePacked(baseURL, svgBase64Encoded)); + } + + function _baseURI() internal pure override returns (string memory) { + return "data:application/json;base64,"; + } + + function tokenURI( + uint256 tokenId + ) public view virtual override returns (string memory) { + if (!_exists(tokenId)) { + revert ERC721Metadata__URI_QueryFor_NonExistentToken(); + } + + string memory imageURI = s_ImageURI; + + return + string( + abi.encodePacked( + _baseURI(), + Base64.encode( + bytes( + abi.encodePacked( + '{"name":"', + name(), // You can add whatever name here + '", "description":"An NFT that changes based on the Chainlink Feed", ', + '"attributes": [{"trait_type": "coolness", "value": 100}], "image":"', + imageURI, + '"}' + ) + ) + ) + ) + ); + } + + function getSVG() public view returns (string memory) { + return s_ImageURI; + } + + function getTokenCounter() public view returns (uint256) { + return s_tokenCounter; + } +} diff --git a/examples/abi.json b/examples/abi.json index be62976..4e7a141 100644 --- a/examples/abi.json +++ b/examples/abi.json @@ -1,9 +1,20 @@ [ { - "inputs": [], + "inputs": [ + { + "internalType": "string", + "name": "Svg", + "type": "string" + } + ], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "ERC721Metadata__URI_QueryFor_NonExistentToken", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -54,6 +65,56 @@ "name": "ApprovalForAll", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "highValue", + "type": "int256" + } + ], + "name": "CreatedNFT", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "highValue", + "type": "int256" + } + ], + "name": "mintNft", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "anonymous": false, "inputs": [ @@ -73,6 +134,82 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "anonymous": false, "inputs": [ @@ -100,6 +237,11 @@ }, { "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, { "internalType": "address", "name": "to", @@ -111,7 +253,20 @@ "type": "uint256" } ], - "name": "approve", + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -154,6 +309,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getSVG", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenCounter", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -223,112 +404,42 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - } - ], - "name": "safeMint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" } ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, + "name": "supportsInterface", + "outputs": [ { "internalType": "bool", - "name": "approved", + "name": "", "type": "bool" } ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [ { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" + "internalType": "string", + "name": "svg", + "type": "string" } ], - "name": "supportsInterface", + "name": "svgToImageURI", "outputs": [ { - "internalType": "bool", + "internalType": "string", "name": "", - "type": "bool" + "type": "string" } ], - "stateMutability": "view", + "stateMutability": "pure", "type": "function" }, { @@ -362,41 +473,5 @@ ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" } -] +] \ No newline at end of file diff --git a/examples/mint.ts b/examples/mint.ts index aaca8a8..9cfd767 100644 --- a/examples/mint.ts +++ b/examples/mint.ts @@ -23,7 +23,7 @@ const demoMintNFT = async () => { console.log("Balance: ", bal.toString()); console.log("Minting New Token"); - const tx = await nftToolbox.writeContract("safeMint", [address]); + const tx = await nftToolbox.writeContract("safeMintNFT", [address]); await tx.wait(); bal = await nftToolbox.readContract("balanceOf", [address]);