5. Connect APIs to your Smart Contracts using Chainlink
Learn how to build a Dynamic NFT that changes based on market data using Chainlink Oracles that pull data from external sources into a smart contract.
What is a dynamic NFT?
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.
About this Dynamic NFT 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.
Let’s hack!
Required Tools and Prerequisites
This tutorial assumes you have some prior coding experience and you’ve followed along with the preceding content on the Welcome to the Road to Web3 program that Alchemy has built for you.
1. IDE
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.
2. Github Repo
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
The main
branch contains the baseline ERC721 Token using the OpenZeppelin Wizard.
The price-feeds branch
The 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
The 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!
3. IPFS Companion
Install the IPFS Companion Browser Extension (for any Chromium based browser).
This will hold your token’s URI and metadata information.
4. Faucets and Testnet Tokens
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.
1. Set up the ERC721 token
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:
i. Create the contract from scratch
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 mintToken()
logic!
ii. Copy smart contract code from the reference Github repo
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 Bull&Bear.sol
.
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.
2. Update the links in the IPFS URIs
Now you need to make sure you update the links in the IPFS URIs for bullUrisIpfs
and 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.
3. Complete a compile check
- 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
- Deploy your contract to the JavaScript VM London environment in-browser
- Copy your Remix-provided wallet address and paste it in the
safeMint
field to mint a token
- Scroll down and click on
tokenURI
with 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!
4. Make your contract Keepers Compatible
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”.
Here are the key steps:
- Import "@chainlink/contracts/src/v0.8/KeeperCompatible.sol"
- Make your contract inherit from
KeeperCompatibleInterface
- 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.
- Implement
checkUpkeep
andperformUpkeep
functions 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
interval
we 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!
Our 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.
We will use the BTC/USD feed proxy contract on Rinkeby, but you can choose another one from the Rinkeby network.
5. Interact with Chainlink Price Feeds
To interact with the Price Feed oracle of choice, we need to use the AggregatorV3Interface
.
Be sure to understand how data feeds work and how to use them.
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?
Mocking Live Net Smart Contracts
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
MockPriceFeed
- Deploy it
It’s simply importing the mock that Chainlink has written for the price feed aggregator proxy.
Note
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: 3034715771688
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.
Call 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 4534715771688
.
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 performUpkeep()
.
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.
Summarizing the Workflow
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 gamer_bull.json
.
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
Click on 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 Bull&Bear
.
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
performUpkeep()
.
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 currentPrice
field.
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
Click on 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 performUpkeep
ourselves.
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 tokenURI
If you click currentPrice
you should see the price based on the updated Mock Price Feed.
Then click 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.
Week 5 Assignment
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.
Here are the steps:
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 requestRandomWords
and fulfillRandomWords
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.
Deploy your Dynamic NFT on Rinkeby
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
setInterval
otherwise the Keepers network will run yourperformUpkeep
far 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.
If
performUpkeep
finds 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 tokenURI
.
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
7) Create and fund a VRF subscription**.
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
tokenURI
and see the updated metadata.You can try and force an update on OpenSea with the
force_update
param but it may not update the images in time. The name of the NFT should be updated at the least.
Conclusion
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 over 1 year ago