A dynamic NFT is a non-fungible token that can change based on certain circumstances.
For example, there are currently eight different LaMelo Ball NFTs, with each NFT recording a different set of LaMelo’s player statistics, from rebounds and assists to points scored, and changing based on these (10 assists? different color - 1 point scored, different ball).
Dynamic NFT holders can receive special access to raffles and other NFT-specific perks based on LaMelo’s ongoing performance.
It gets even cooler;
One of these eight NFTs, the Gold Evolve NFT, came with a unique promise:
If LaMelo Ball won Rookie of the Year for the 2021 NBA season, the NFT itself would evolve to reflect a new image. LaMelo won the award and the NFT evolved.
Let's start the tutorial.
In this tutorial, you're going to build a Dynamic NFT using Chainlink’s decentralized and cryptographically secured oracle network to get and track asset price data.
Then, you will use the automations from the Chainlink Keepers Network to automate your NFT smart contract to update the NFTs according to the asset price data you're tracking.
If the market price moves up, the smart contract will randomly pick the NFT’s URI to point to one of these three bullish images and the NFT will be dynamically updated:
If the price feed’s data moves down, the NFT will dynamically update to one of these bearish images, which are also randomly selected!
(Credits: the Chainlink design team)
Finally, we will use Chainlink’s Verifiable Random Function to add cryptographically guaranteed randomness to our NFT smart contract to randomly select an NFT image from a list of options.
If you're a visual learner, you can follow the full video tutorial on how to connect APIs to smart contracts using Chainlink from the Alchemy YouTube channel.
In this tutorial, we're going to use the Remix IDE and the built-in “London VM” blockchain network, but the same can be done using Hardhat or any other Solidity Smart Contract development framework and your favorite code editor.
Here, there is a Github repo for the Dynamic NFT tutorial that we made for you.
The repo reflects the structure we will follow.
The main branch
main branch contains the baseline ERC721 Token using the OpenZeppelin Wizard.
The price-feeds branch
price-feeds branch adds the Chainlink Keepers implementation and connects to the Chainlink Asset price data that we'll use to track a specific asset’s price.
The randomness branch
randomness branch contains the logic to add randomness so that our Dynamic NFT is chosen randomly from the NFT metadata URIs we have in our smart contract.
This bit is for you to do as a special assignment to build your skills!
Install the IPFS Companion Browser Extension (for any Chromium based browser).
This will hold your token’s URI and metadata information.
Make sure your MetaMask wallet is connected to Rinkeby.
Once your wallet is connected to Rinkeby, get Rinkeby ETH from Alchemy's Rinkeby faucet.
You will also need to get testnet LINK tokens.
For your assignment, you will add randomness, but you’ll deploy to Ethereum's Goerli testnet.
If you need Goerli testnet tokens, get Goerli ETH from Alchemy's Goerli faucet.
We'll start with OpenZeppelin and Remix to create an NFT smart contract.
As this has already been covered, you have two options to get your NFT smart contract:
Follow the Welcome to the Road to Web3 and remember to name the NFT with “Bull&Bear”.
Then give it a Symbol name of “BBTK” and read the bit below carefully to update the
This is probably the fastest way: copy over the smart contract code from the reference repo’s main branch and save it in Remix as
Note how we have added the references to the arrays of IPFS URIs and also updated the
safeMint() method to set an initial token URI as a starting point.
Now you need to make sure you update the links in the IPFS URIs for
bearUrisIpfs to point to the files hosted on your IPFS browser node.
To set up the NFT data on your browser IPFS node:
- Copy the Token metadata JSON and image files from this folder in the repo
- Click on the IPFS Companion’s browser extension icon to open up your local IPFS node
- Import all those files to the FILES section of your node
- To get the URI link, click on the three dots and copy the string from “Share Link”.
You’ll need both JSONs and PNGs in your IPFS node, but only the JSON files for your smart contract because the JSON files point to the PNGs.
- Choose the right compiler in Remix based on the smart contract’s pragma ( 0.8.0 onwards)
- Make sure you’re compiling the right file- Bull&Bear.sol
- Copy your Remix-provided wallet address and paste it in the
safeMintfield to mint a token
- Scroll down and click on
tokenURIwith the argument “0”.
Because your first token has a token ID of zero, it will return the tokenURI that points to the ‘gamer bull` JSON file.
Great - your NFT smart contract is working!
Now, we can make our NFT Contract not just dynamic, but automatically dynamic!
This code is referenced in the price-feeds branch of the repo.
First, we add the automation layer with Chainlink Keepers, which means we need to extend our NFT smart contract to make it “Keepers Compatible”.
- Import "@chainlink/contracts/src/v0.8/KeeperCompatible.sol"
- Make your contract inherit from
- Adjust your constructor to take in an interval period that gets set as a contract statevariable and this sets the intervals at which the automation will occur.
performUpkeepfunctions in the NFT smart contract so that we satisfy the interface.
- Register the “upkeep” contract with the Chainlink Keeper Network.
The Chainlink Keepers Network will check our
checkUpkeep() function every time a new block is added to the blockchain and simulate the execution of our function off-chain!
That function returns a boolean:
- If it's false, that means no automated upkeep is due yet.
- If it returns true, that means the
intervalwe set has passed, and an upkeep action is due.
The Keepers Network calls our
performUpkeep() function automatically, and run logic on-chain.
No developer action is needed.
It’s like magic!
checkUpkeep will be straightforward because we just want to check if the
interval has expired and return that boolean, but our
performUpkeep needs to check a price feed.
For that, we need to get our Smart Contract to interact with Chainlinks price feed oracles.
To interact with the Price Feed oracle of choice, we need to use the
In our reference code in the
price-feeds branch, the constructor accepts the address of the aggregator oracle as a parameter in the constructor. Accepting a parameter at deploy time is super useful as it makes it configurable when we develop locally.
To interact with a live oracle on Rinkeby, our contract needs to be deployed to Rinkeby. That's necessary for integration testing but while developing it slows us down a bit.
How do we speed up our local edit-compile-debug development loop?
Instead of constantly re-deploying to a test network like Rinkeby, paying test ETH, etc, we can (while iterating on our smart contract) use mocks.
For example we can mock the price feed aggregator contract using this mock price feed contract.
The advantage is that we can deploy the mock in our Remix, in-browser London VM environment and adjust the values it returns to test different scenarios, without having to constantly deploy new contracts to live networks, then approve transactions via MetaMask and pay test ETH each time.
Here's what to do:
- Copy that file over to your Remix
- Save it as
- Deploy it
It’s simply importing the mock that Chainlink has written for the price feed aggregator proxy.
you must change the compiler to 0.6.x to compile this mock.
When deploying a mock you need to pass in the decimals the price feed will calculate prices with.
You can find these from a list of price feed contract addresses, after clicking “Show More Details”.
The BTC/USD feed takes in 8 decimals.
You also need to pass in the initial value of the feed.
Since I randomly chose the BTC/USD asset price, I passed it an old value I got when I was testing:
When you deploy it locally, be sure to note the contract address that Remix gives you.
This is what you pass into the constructor of your NFT Smart Contract so that it knows to use the mock as the price feed.
You should also play around with your locally deployed mock price feed.
latestRoundData to see the latest price from the mock price feed, and other data that conforms to the Chainlink Price Feed API.
You can update the price by calling
updateAnswer and passing in a higher or lower value (to simulate the rise and fall of prices).
You could make the price drop by passing
2534715771688 or rise by passing in
Super handy for in-browser testing of your NFT smart contract!
Going back to the NFT smart contract, be sure to update it to reflect the reference code.
So here’s what I suggest you do:
- First read this short doc on how to make our NFT smart contract Keepers compatible
- Read the simple way to use data feeds
- Deploy the mock data feed
- Read its source code to understand how Chainlink Price Feed smart contracts are written
Once you've read these resources, give it a shot yourself.
If you want to jump straight to our implementation, it is on the price-feeds branch.
Note that we set the
price feed as a public state variable so we can change it, using the
setPriceFeed() helper method, and we also added the key dynamic NFT logic to
Every time the Chainlink Keepers network calls that, it will execute that logic on-chain and if the Chainlink Price Feed reports a price different from what we last tracked, the URIs are updated.
This demo doesn’t optimize for the gas costs of updating all Token URIs in the smart contract. We focus on how NFTs can be made dynamic.
The costs of updating all NFTs that are in circulation could be extremely high on the Ethereum network so consider that carefully, and explore layer 2 solutions or other architectures to optimize gas fees.
When you’ve done all this, this is what your testing workflow will look like:
1) Deploy the Mock Price Feed-in Remix
You can use constructor arguments
8,3034715771688 to start with and copy its address.
Remember to set the Remix compiler to the 0.6.x range for this.
2) Re-deploy the
Bull&Bear smart token contract
Remember to update the compiler version.
For the constructor arguments, you can pass in 10 seconds for the interval and the Mock Price Feed’s address as the second argument.
3) Mint a token or two
Mint a token or two and check their
tokenURIs by clicking on
tokenURI after passing 0, 1, or whatever the minted token ID you have is.
The token URI should all default to the
4. Check the NFT contract's constructor
Check that the NFT contract’s constructor is called
getLatestPrice() and that in turn updates the
currentPrice state variable.
Do this by clicking on the
currentPrice button - the result should match the price you set in your Mock Price Feed.
5) Pass in an empty array
checkUpkeep and pass in an empty array (
) as the argument. It should return a boolean of true because we passed in 10 seconds as the
interval duration and 10 seconds would have passed from when you deployed
The reference repo includes a setter function so you can update the interval field for convenience.
Keep in mind that when you deploy to Rinkeby you want to set the interval to longer - each feed updates its aggregated prices at configured intervals or if the price deviates by a set threshold.
If you configure your Keepers checks too often, it'll be a waste of your test LINK tokens.
That’s why for mocking we pass it a very short interval of 10 seconds, because we don’t expend test LINK, and also because we can quickly run
6) Ensure the Mock Price Feed is updated
Make sure that your Mock Price Feed is updated to return a price that is different from what you currently have stored in your NFT Smart Contract’s
If you update the Mock Contract with a lower number, for example, you would expect that your NFT Smart Contract would switch the NFTs to show a “bear” token URI.
7) Simulate your contract being called
performUpkeep after passing it an empty array. This is how you simulate your contract being called by the Chainlink Keepers Network on Rinkeby.
Don’t forget, you get to deploy to Rinkeby and register your upkeep and connect to Rinkeby Price feeds as part of your assignment.
Since right now we are on the Remix in-browser network we need to simulate the automation flow by calling
8) Check the latest price and update all token URIs
performUpkeep should check the latest price and update all token URIs.
This is instantaneous in the Remix browser. On Rinkeby this can take some time.
You don’t need to sign any transaction in MetaMask when doing it locally, but when you connect to Rinkeby you will have MetaMask ask you to sign transactions for each step.
9) Refresh the
currentPrice and check the
If you click
currentPrice you should see the price based on the updated Mock Price Feed.
tokenURI again, and you should see that your token URI has changed.
If the price dropped below the previous level it would be switched to a bear.
If the last token URI was a bear and the price increased, it should switch to a bull token URI.
This assignment uses a new tool: the Chainlink Verifiable Random Function.
This tool provides cryptographically provable randomness and is widely used in gaming and other applications where provable and tamper-resistant randomness is essential to fair outcomes.
Right now, we have hard coded which token URI shows up - the first URI (index 0) in the array. We need to make it a random index number so a random NFT image shows up as the token URI.
1) Review a Chainlink VRF example
Look at the super brief example usage of Chainlink VRF - you have to implement only two functions to get cryptographically provable randomness inside the NFT Smart Contract.
2) Update your NFT smart contract to use two VRF functions
Update your NFT smart contract to use
3) Use the VRF mock in the randomness branch
Use the VRF mock provided in the randomness branch of the reference repo, and make sure you carefully read the commented out instructions in the VRF mock so you know exactly how to use it.
Lastly, once you’ve played around with the NFT smart contract and got it to change the tokenURI dynamically a few times in Remix, connect Metamask and Remix to Rinkeby and deploy the NFT.
When you deploy the NFT to Rinkeby, you can still use the mocks, but you need to deploy them too and in the right order.
Complete the following in the right order:
1) Connect your Metamask to Rinkeby
2) Acquire test LINK and test ETH from the Chainlink Faucet **
If you’re planning to deploy the mock price feed aggregator and update it to the Chainlink Rinkeby price feed later, deploy the mock now. Likewise, if you intend to test on Rinkeby using the mock VRF Coordinator, you must deploy it on Rinkeby.
3) Deploy the NFT smart contract to Rinkeby
Make sure you pass in the right constructor parameters.
If you’re using the mocks, make sure they’re deployed first so you can pass their Rinkeby addresses to the NFT contract’s constructor.
If you’re using a Chainlink live price feed, then its address must be as per the reference repo or whatever Rinkeby price feed address you choose from here.
Since you can connect your Remix “environment” to the deployed NFT contract on Rinkeby, and call the NFT contract’s
performUpkeep from Remix, you can keep the interval short for the first test run.
Remember to increase the interval by calling
setIntervalotherwise the Keepers network will run your
performUpkeepfar more often than the Price Feed will show new data.
You can also change your price feed address by calling
setPriceFeed and passing in the address you want it to point to.
performUpkeepfinds that there is no change in price, the token URIs will not update!.
4) Mint your first token, and check its URI via Remix
It should be the
gamer_bull.json. Check on OpenSea if you like!
5) Play around with the mock values
If you’re using the two mocks, play around with the values and see the changes to the NFTs by calling
6) Switch to the live Chainlink contracts on Rinkeby
When you’re ready to switch to the live Chainlink contracts on Rinkeby, update the price feed’s address and the
vrfCoordinator in the NFT contract by calling their setter functions.
6) Register your NFT smart contract
Next, register your NFT smart contract that is deployed to Rinkeby as a new “upkeep” in the Chainlink Keepers Registry
If you’re using the live Rinkeby Chainlink VRF make sure you call
setVrfCoordinator() so you’re no longer using your VRF Mock on Rinkeby.
If you’ve not implemented it, that’s part of your learning, and you can check the reference repo.
8) Check OpenSea in an hour or two
Depending on how often the prices change (and if you want to immediately, then keep using the mocks on Rinkeby).
OpenSea caches metadata and it may not show for a while even though you can call
tokenURIand see the updated metadata.
You can try and force an update on OpenSea with the
force_updateparam but it may not update the images in time. The name of the NFT should be updated at the least.
Congrats! You have coded up a Dynamic NFT that reflects real-world price feed data, and with cryptographically proven, tamper-resistant randomness deciding dynamic NFT images!
If you want to explore other amazing use cases for this powerful technology, check out 16 Ways to Create Dynamic NFTs Using Chainlink Oracles.
Updated 7 months ago