Create a Dynamic NFT
This guide will walk you through creating a dynamic NFT with Space and Time and Chainlink Functions
Introduction
This guide will walk you through creating a dynamic NFT using Space and Time, and Chainlink Functions. We will create a SwordNFT that's rating changes based on how the sword is being used in-game. Gaming telemetry in Space and Time is updated and fed to the NFT contract using Chainlink Functions.
Here is an example of what the NFT will look like when we're done.
Before we get started, we should answer a couple of important questions.
What is a dynamic NFT (dNFT)?
From a simple view, according to ERC721 (and ERC1155) non-fungible tokens should have a tokenURI
function that stores a URL which will return a JSON blob of metadata for a given NFT. That blob usually contains things like a pointer to an image file, NFT name, description, etc. A dNFT is simply an NFT where the metadata for the NFT is designed to change.
But I thought NFTs weren't supposed to change!
There is nothing in ERC721/ERC1155 that says an NFT's metadata cannot change. The idea that an NFT's metadata shouldn't change likely comes from one specific use case involving permanent digital collectibles. While a full discussion on the topic is beyond the scope of this guide, it's important to understand that NFTs can take on many forms, and there is an increasing demand for NFTs that can evolve and/or be leveled up. Also, keep in mind that it's important to think about who (or what) can make changes to an NFT, how those changes can be made, and how long it's possible to make changes.
Overview
For the guide we will go through the following high-level steps:
- Base Setup & Config
- Prerequisites
- Download & install repo
- Setup
env-enc
- Space and Time Setup
- Create Table
- Insert Data
- Connect SxT to Mumbai via Chainlink Functions
- Level Up Your SwordNFT
1. Base Setup & Config
Space and Time and Chainlink Functions are both in beta. It's probable that updates will be made to one or both that change how steps in this guide work. You can find us on Discord with questions.
Prerequisites
-
You will need beta access to Space and Time and Chainlink Functions.
-
If you're new to SxT, it's recommended that you start first with our SxT Getting Started Guide. If you're new to Chainlink Functions, we recommend you start with their Getting Started Guide. Having a basic understanding of how to connect to SxT and how Chainlink Functions work will set you up for success with this guide.
-
You will obviously need a wallet address on Polygon Mumbai testnet, with token balances of 2 LINK and 0.2 MATIC.
Setup
Open up a terminal / command window, navigate to a folder where you'd like to run this demo from, and enter the following commands into the terminal window:
-
Clone the demo repo from github, which includes all solidity and js files:
git clone https://github.com/SxT-Community/SxT-dNFT.git && cd SxT-DNFT
If you don't have git installed, this page has install instructions.. -
Install all node.js dependancies by running:
npm install
-
One of the packages you install is a handy tool created by Chainlink Labs for encrypting your local environment variables. Please have a look at
npx env-enc help
to see all available commands.
-
Enter
npx env-enc set-pw
to set your root password (or unlock your encrypted envar file) -
Enter
env-enc set
to set the following environment variables, which will be used by the above demo. The commandline app will ask for a variable NAME, then VALUE. For each, enter:POLYGON_MUMBAI_RPC_URL
- RPC_URL for Mumbai network (Infura, Alchemy, etc) used by CL Functions.
For example, check out the first two sections of Infura's 'Getting Started' docs. The RPC_URL you'll need will look something like:https://polygon-mumbai.infura.io/v3/your_api_key
MUMBAI_RPC_URL
- RPC_URL used by Hardhat - same URL as above. As of this writing, Hardhat and Chainlink have different environment variables for the RPC endpoint. The value should be 100% identical.PRIVATE_KEY
- Private key for your wallet address (with aforementioned 2 LINK and 0.2 MATIC), and should look something like4bb23044e2b4e1b8e3793d6686306915adcc25e35211f08f91c462b36250ac99
POLYGONSCAN_API_KEY
- API Key for Polyscan - optional, but recommended to verify contracts.
The API key should look something like:G9JRV6CBJWYMPUGQ7382RKUBNINI44QRUF
API_KEY
- API Key for Space and Time - required to execute off-chain queries.
The API key should look something like:sxt_dMFtbHgkW5_59sND2XMSXuEeOtXzNalSKbvZ
API_URL
- Space and Time API URL, which contains the gateway / secrets proxy endpoint, where the query is sent. The URL should look something like:https://proxy.api.spaceandtime.app/v1/sql
2. Space and Time Setup
Setup Data in Space and Time
There are many ways to connect to Space and Time, however the easiest and fastest is via Space and Time Studio.
Here's a look at the gaming telemetry table SxT we're going to use:
Table: SXTNFT.GAME_TELEMETRY_ARTHUR
ID | GamerId | ActionType | AchievementId | collectableId | Level | ItemId | points |
---|---|---|---|---|---|---|---|
1 | 1 | Game Started | 1 | SwordNFT | 3 | ||
SwordNFT | 1 | Attack | 1 | SwordNFT | 3 | ||
SwordNFT | 1 | Defense | 1 | SwordNFT | 2 | ||
4 | 1 | Kill | King | 1 | SwordNFT | 100 | |
5 | 1 | Attack | 1 | SwordNFT | 3 | ||
6 | 1 | Collect | PotionA | 2 | SwordNFT | 100 | |
7 | 1 | Collect | PotionB | 2 | SwordNFT | 150 | |
8 | 1 | Attack | 3 | SwordNFT | 9 |
- The table above is
Public_Write
meaning anyone can read and modify data. This is ideal for a public demo since it means you can use this table for your demo as-is. However, others can also, and you may have others working on the demo simultaneously, leading to unexpected results. If you want to create your version of the table, use this recipe for creating tables with this SQL:
CREATE TABLE <SomeOtherSchema>.GAME_TELEMETRY_ARTHUR (
ID INTEGER,
GamerId INTEGER,
ActionType VARCHAR,
AchievementId VARCHAR,
collectableId VARCHAR,
Level_ INTEGER,
ItemId VARCHAR,
Points INTEGER,
PRIMARY KEY (ID)
) WITH "public_key=<your_biscuit_public_key>,
access_type=public_write,
template=PARTITIONED,
atomicity=transactional"
- Delete any records that may be in the table already using the DML endpoint:
Delete From SXTNFT.GAME_TELEMETRY_ARTHUR
- INSERT data - This is where a game would be inserting its telemetry into SxT:
INSERT INTO SXTNFT.GAME_TELEMETRY_ARTHUR (ID, GamerId, ActionType, AchievementId, collectableId, Level_, ItemId, Points)
VALUES
(1, 1, 'Game Started', '', '', 1, 'SwordNFT', 3),
(2, 1, 'Attack', '', '', 1, 'SwordNFT', 3),
(3, 1, 'Defense', '', '', 1, 'SwordNFT', 2),
(4, 1, 'Kill', 'King', '', 1, 'SwordNFT', 100),
(5, 1, 'Attack', '', '', 1, 'SwordNFT', 3);
- View data that is loaded:
SELECT * from SXTNFT.GAME_TELEMETRY_ARTHUR
Also, we can run logic to determine the SwordNFT's current level:
SELECT /*! USE ROWS */
ItemId,
SUM(Points),
CASE
WHEN SUM(Points) BETWEEN 100 AND 150 THEN 1
WHEN SUM(Points) BETWEEN 151 AND 300 THEN 2
WHEN SUM(Points) > 300 THEN 3
ELSE ''
END AS SWORD
FROM SXTNFT.GAME_TELEMETRY_ARTHUR
GROUP BY ItemId;
Should return:
ITEMID |SUM(POINTS)|SWORD|
--------+-----------+-----+
SwordNFT|111 |1 |
3. Connect SxT to Mumbai via Chainlink Functions
Now that we have our gaming telemetry table in SxT, we're going to connect everything up. The following steps were adapted from the Chainlink Functions repo and might be useful as a resource if you run into any issues getting Chainlink Functions setup.
The first thing we're going to do is simulate the full interaction. This is helpful because it allows us to identify potential issues before we deploy our dNFT contract.
In your terminal window (same one where you set your environment variables above via env-enc set
) enter the following steps:
-
Test/Simulate
npx hardhat functions-simulate-script
-
Deploy our contract to Mumbai:
npx hardhat functions-deploy-consumer --network polygonMumbai --verify true
-
Get the contract address from the result of the previous step and set a temporary envar:
export CONTRACT_ADDRESS=<your_contract_address>
-
Create and fund a new Functions billing subscription using the Chainlink Functions UI and add the deployed consumer contract as an authorized consumer to your subscription. You can also do this programmatically with:
npx hardhat functions-sub-create --network polygonMumbai --amount 2 --contract $CONTRACT_ADDRESS
Get the subscription id and set a shell envar for:
export SUB_ID=<CL_FUNCTIONS_SUB_ID>
-
Prompt the NFT to call Chainlink Functions request to query Space and Time:
npx hardhat functions-request --network polygonMumbai --contract $CONTRACT_ADDRESS --subid $SUB_ID
- Now is an excellent time to pull up your dNFT contract on Rarible!
- Enter into the terminal:
open https://testnet.rarible.com/token/polygon/$CONTRACT_ADDRESS:0
- or go to the URL directly, replacing
$CONTRACT_ADDRESS
with the contract address from step #3
- Enter into the terminal:
- NOTE: If you get an error about your contract not being added to the subscription, you can add it as a Consumer to your Subscription via the web UI or the CLI like this:
npx hardhat functions-sub-add --network polygonMumbai --contract $CONTRACT_ADDRESS --subid $SUB_ID
4. Level Up Your dNFT Sword
Now it's time to level up your swordNFT based on new gaming telemetry loaded into Space and Time.
Add more game telemetry to SxT (sword 2)
INSERT INTO SXTNFT.GAME_TELEMETRY_ARTHUR (ID, GamerId, ActionType, AchievementId, collectableId, Level_, ItemId, Points)
VALUES (6, 1, 'Collect', '', 'PotionA', 2, 'SwordNFT', 100);
SELECT /*! USE ROWS */
ItemId,
SUM(Points),
CASE
WHEN SUM(Points) BETWEEN 0 AND 150 THEN 1
WHEN SUM(Points) BETWEEN 151 AND 300 THEN 2
WHEN SUM(Points) > 300 THEN 3
ELSE ''
END AS SWORD
FROM SXTNFT.GAME_TELEMETRY_ARTHUR
GROUP BY ItemId;
Game prompts NFT to re-query Space and Time
-
Test/Simulate
npx hardhat functions-simulate-script
-
Prompt the NFT to call Chainlink Functions request to query Space and Time:
npx hardhat functions-request --network polygonMumbai --contract $CONTRACT_ADDRESS --subid $SUB_ID
-
Let's look at the NFT again, to see it change! Just enter into the terminal:
open https://testnet.rarible.com/token/polygon/$CONTRACT_ADDRESS:0
or go to the URL directly, replacing$CONTRACT_ADDRESS
with the contract address from step #3
Add More Game Telemetry to SxT (sword 3)
As the game is played, more telemetry is loaded into Space and Time!
INSERT INTO SXTNFT.GAME_TELEMETRY_ARTHUR(ID, GamerId, ActionType, AchievementId, collectableId, Level_, ItemId, Points)
VALUES
(7, 1, 'Collect', '', 'PotionB', 2, 'SwordNFT', 150),
(8, 1, 'Attack', '', '', 3, 'SwordNFT', 9);
View the insert with the following:
SELECT * from SXTNFT.GAME_TELEMETRY_ARTHUR
Or:
SELECT /*! USE ROWS */
ItemId,
SUM(Points),
CASE
WHEN SUM(Points) BETWEEN 100 AND 150 THEN 1
WHEN SUM(POINTS) BETWEEN 151 AND 300 THEN 2
WHEN SUM(POINTS) > 300 THEN 3
ELSE 1
END AS SWORD
FROM SXTNFT.GAME_TELEMETRY_ARTHUR
GROUP BY ItemId;
Push New Telemetry to Mumbai
-
Test/Simulate
npx hardhat functions-simulate-script
-
Prompt the NFT to call Chainlink Functions request to query Space and Time:
npx hardhat functions-request --network polygonMumbai --contract $CONTRACT_ADDRESS --subid $SUB_ID
Head back to Rarible to see your level three swordNFT!
Updated about 1 year ago