Deploying Your Dynamic NFTs

In this section, you will be deploying and interacting with your Dynamic NFTs

You will be creating a dynamic NFT of a flower with 3 stages: Seed, Seedling, and Bloom.

Please make sure you've done Section 2 Smart Contract fundamentals before you attempt this, as we use tools set up in that section.

Dynamic NFT with 3 Stages: Seed, Seedling, Bloom

Create Our DynamicNFT contract

First, open Remix and create a new workspace, then create a new file called Flowers.sol

Select Create to generate a new workspace
Create a new file named 'Flowers.sol'

This will open an empty file on the right side of your screen which is the IDE. Here you can paste the following code.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "@chainlink/contracts/src/v0.8/AutomationCompatible.sol";
import "@openzeppelin/contracts@4.6.0/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts@4.6.0/utils/Counters.sol";

contract Flower is ERC721, ERC721URIStorage, AutomationCompatibleInterface {
    using Counters for Counters.Counter;

    Counters.Counter public tokenIdCounter;
 
   // Metadata information for each stage of the NFT on IPFS.
    string[] IpfsUri = [
        "<https://ipfs.io/ipfs/QmVCZ6hVENjkdCDLMoovNCR5S6bSisUQnBKPibb8XXSJqF/seed.json>",
        "<https://ipfs.io/ipfs/QmVCZ6hVENjkdCDLMoovNCR5S6bSisUQnBKPibb8XXSJqF/purple-sprout.json>",
        "<https://ipfs.io/ipfs/QmVCZ6hVENjkdCDLMoovNCR5S6bSisUQnBKPibb8XXSJqF/purple-blooms.json>"
    ];

    uint public immutable interval;
    uint public lastTimeStamp;

    constructor(uint updateInterval) ERC721("Flower Bootcamp 2024", "FLO") {
        interval = updateInterval;
        lastTimeStamp = block.timestamp;
        safeMint(msg.sender);
    }

    function safeMint(address to) public {
        uint256 tokenId = tokenIdCounter.current();
        tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, IpfsUri[0]);
    }

    // determine the stage of the flower growth
    function flowerStage(uint256 _tokenId) public view returns (uint256) {
        string memory _uri = tokenURI(_tokenId);
        // Seed
        if (compareStrings(_uri, IpfsUri[0])) {
            return 0;
        }
        // Sprout
        if (
            compareStrings(_uri, IpfsUri[1])
        ) {
            return 1;
        }
        // Must be a Bloom
        return 2;
    }

    function growFlower(uint256 _tokenId) public {
        if(flowerStage(_tokenId) >= 2){return;}
        // Get the current stage of the flower and add 1
        uint256 newVal = flowerStage(_tokenId) + 1;
        // store the new URI
        string memory newUri = IpfsUri[newVal];
        // Update the URI
        _setTokenURI(_tokenId, newUri);
    }

    // helper function to compare strings
    function compareStrings(string memory a, string memory b)
        public pure returns (bool)
    {
        return (keccak256(abi.encodePacked((a))) ==
            keccak256(abi.encodePacked((b))));
    }

    function checkUpkeep(bytes calldata /* checkData */) external view override returns (bool upkeepNeeded, bytes memory /* performData */) {
        if ((block.timestamp - lastTimeStamp) > interval ) {
            uint256 tokenId = tokenIdCounter.current() - 1;
            if (flowerStage(tokenId) < 2) {
                upkeepNeeded = true;
            }
        }
        // We don't use the checkData in this example. The checkData is defined when the Upkeep was registered.
    }

    function performUpkeep(bytes calldata /* performData */) external override {
        //We highly recommend revalidating the upkeep in the performUpkeep function
        if ((block.timestamp - lastTimeStamp) > interval ) {
            uint256 tokenId = tokenIdCounter.current() - 1;
            if (flowerStage(tokenId) < 2) {
                lastTimeStamp = block.timestamp;            
                growFlower(tokenId);
            }
        }
        // We don't use the performData in this example. The performData is generated by the Automation's call to your checkUpkeep function
    }
    
    function tokenURI(uint256 tokenId)
        public view override(ERC721, ERC721URIStorage) returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    // The following function is an override required by Solidity.
    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage)
    {
        super._burn(tokenId);
    }

}

Compile

Next we want to make sure that the contract has compiled with the correct version, so head to the Solidity Compiler tab on the left side of remix. Here we will ensure Auto compile is checked.

Select 'Auto Compile'

Deploy & Run Transaction

Next we navigate to the Deploy & Run Transactions tab and select our enviroment to Injected Provider - Metamask. We must ensure that we are connected to Sepolia test network.

Select Injected Provider and ensure you are on Sepolia Test Network

Then, in the updateInterval field we want to input 120. This represents the interval between each attempt by Chainlink Automation to "trigger" the contract to update the NFT's metadata from seed stage to flower stage . More on Chainlink Automation's operations later in this page.

After that we want to deploy the contract by clicking the orange transact button and confirming the Metamask transaction.

Set updateInterval paramter to 120 then click transact

In our constructor, we are minting an NFT to the deployer (your wallet address), so once the contract is deployed you should see that the balanceOf for your address will be 1.

You now have 1 minted NFT to your wallet

You can view this NFT on Opensea by copying your contract address, and pasting it in the Opensea searchbar

Paste your contract address in the Opensea search bar
Your minted NFT on Opensea

Before we use Chainlink Automation, let's manually call the growFlower function in our smart contract on remix, passing in the _tokenId of 0 which is the NFT we have just minted (since it's the first NFT minted!). This will cause our seed to change into a seedling.

Call growFlower passing in '0' as a paramater

Then we will go back to Opensea to view our NFT and click the 3 dots in the top right corner, and select refresh metadata. This will tell Opensea to refresh the image of our NFT.

Select 'Refresh Metadata'

We can also force refresh by doing the following in our browser: Right Click → Inspect → Application → Storage → Clear Site Data

Select 'Clear Site Data'

Once you’ve done both of these steps you should now be able to refresh the Opensea page and see that your seed has now changed into a seedling.

Refresh the page to see your Seedling

Next, we will integrate Chainlink Automation to automatically do this process for us, so we don’t have to manually call growFlower.

First, we will go to remix and call safeMint passing in your wallet address, this will mint you a new NFT. In Opensea you will now see that you have 2 NFTs, and your newly minted NFT (token id 1, since its the second minted NFT) will be a seed.

Pass your wallet address as a parameter to 'safeMint' and click transact

Then we will head to https://automation.chain.link/. Make sure you're Metamask is still connected to Ethereum Sepolia, and select Register new Upkeep

Select 'Register new Upkeep'

We will use Custom Logic for this example, so select that option and click next.

Select 'Custom Logic'

In Target contract address put the address of your deployed NFT contract from Remix, and click next.

Paste your contract address here

This will then bring you to the Upkeep Details page, where you can give your upkeep a name. Also, ensure that your Admin Address is the wallet you are currently using in Metamask. You will also need to fund your upkeep with at least 3 LINK. If you don’t have any LINK you can obtain some from the Chainlink Faucet.

Fill in Upkeep Details

Lastly you want to click Register Upkeep at the bottom of the page and confirm the transactions. You will see a pop-up like this appear.

Wait for transaction to be confirmed

After a few seconds, it will update once it’s been finalised and the button will appear with View Upkeep which you should click.

When we deployed our smart contract, we'd put 120 seconds as the updateInterval. So every 2 minutes, Chainlink Automation will call checkUpkeep() in our smart contract to run the custom logic we have written there. So after a couple of minutes, if you head back to Opensea and look at your minted NFT select refresh metadata and reset your cache as we did before. You will then see that your NFT has started changing and eventually, it will be fully bloomed.

Your new NFT will have started blooming automatically using Chainlink Automation

Last updated