Guide: Dynamic NFT Based on Local Weather
2023-11-08
How to Build Dynamic NFTs Based on Weather Conditions
The ability to mint and dynamically update Non-Fungible Tokens (NFTs) based on real-world data is an easy concept to grasp. In this guide, we'll walk you through the process of creating an NFT that updates its image based on the weather conditions of the local city assigned to the NFT at mint. This is achieved through Phala’s Phat Contract 2.0 that checks the weather and determines the appropriate image to set for the NFT’s token URI.
Design
The user journey consists of two distinct flows. The first involves minting an NFT and selecting a city for weather tracking. In the second, users can check the weather and, if conditions change, update the NFT's token URI image.
1) Mint Weather NFT User Flow
First the user will select a valid city to request a new mint of a WeatherMojo NFT. The weather of the selected city is fetched via an HTTPS request to the wttr.in API. Once the results are returned, the IPFS image is selected to correlate to the current weather conditions, and the metadata for the new NFT will be formatted into a JSON string. Next, the JSON string will be stored in an S3 bucket on IPFS via 4Everland API and the tokenURI
will be returned as part of the response to the Consumer Contract on Polygon. Whenever the response is received, the Consumer Contract will then try to mint a new WeatherMojo NFT to the user account that requested the WeatherMojo NFT.
2) Update Weather NFT User Flow (Sunny → Cloudy)
First the user will select the valid city of their minted WeatherMojo NFT and request to update the weather. The weather of the selected city is fetched via an HTTPS request to the wttr.in API. Once the results are returned, the IPFS image is selected to correlate to the current weather conditions, and the metadata for the current NFT will be formatted into a JSON string. Next, the JSON string will be stored in an S3 bucket on IPFS via 4Everland API and the tokenURI
will be returned as part of the response to the Consumer Contract on Polygon. Whenever the response is received, the Consumer Contract will then emit an event of the updated metadata for WeatherMojo NFT of the requesting user.
Setting Up 4Everland S3 Storage on IPFS
Note: This step requires having an account and generating an API key to interact with the S3 HTTP API. For more info https://www.4everland.org/.
First, we need to create an S3 Bucket in the 4Everland Dashboard. This will be located on the left side panel of the website where you will select Storage Bucket(S3). You will then select + New Bucket
and create a new S3 Bucket. In this example, I create weather-mojo
for the name.
After clicking OK
, you will see your new bucket created below. The weather-mojo
S3 Bucket will store the latest information for each of our WeatherMojo
ERC-721 NFT’s metadata.
Developer Journey
Step 1: Minting the NFT
The first step in this process is to mint your NFT. This NFT will be unique in that its image will be determined by the current weather conditions.
- Open the mumbai.polygonscan.com and navigate to the
request
function. Then submit a city. I useAomori
for this example.
- Select the
Transactions
tab to view therequest
. Seconds later, the Phat Contract will fetch the weather information, update the S3 Bucket with the NFT metadata for the city, and return a response to the Consumer Contract with a callMeta TX Rollup U256Cond Eq
.
- Confirm your new WeatherMojo NFT was minted successfully in the NFT marketplace of your preference. This example shows the minted NFT in ThirdWeb’s NFT Contract dashboard.
Congratulations! You've just minted your first weather-based NFT.
Step 2: Updating the NFT Image Based on Local Weather
Now that you've obtained your NFT, let's configure it to update its image according to the weather conditions in your selected city.
- Before we update the NFT, check out the current weather for a WeatherMojo NFT previously minted. I use
MexicoCity
NFT since theAomori
weather may not deviate from the original mint.
- Open the mumbai.polygonscan.com and navigate to the
updateWeather
function. Then submit a city. I useMexicoCity
for this example.
- Like in the mint NFT step, select the
Transactions
tab to view theupdateWeather
transaction has executed successfully. Seconds later, the Phat Contract will fetch the weather information, update the S3 Bucket with the NFT metadata for the city, and return a response to the Consumer Contract with a callMeta TX Rollup U256Cond Eq
.
- Now check the weather conditions for your city and check if the NFT metadata updated accordingly.
Step 3: Understanding the Phat Contract Logic
The magic behind this dynamic NFT lies in a Phat Contract. It assesses the weather in your city and selects the appropriate image for your NFT.
- The off-chain program runs in the background, periodically checking the weather conditions for your city if there is a request for a new NFT mint or to update the weather for an existing minted city. The code to fetch the weather is below:
function fetchWeatherApi(apiUrl: string, city: string): any {
const weatherFormat = '?format={"name":"%l","description":"Weather+in+%l","external_url":"https://wrlx-bucket.4everland.store/%l/weather","image":"%x","attributes":[{"trait_type":"timestamp","value":"%T+%Z"},{"trait_type":"city","value":"%l"},{"trait_type":"weather","value":"%C+%t"}]}';
const httpUrl = `${apiUrl}${city}${weatherFormat}`;
let headers = {
"Content-Type": "application/json",
"User-Agent": "phat-contract",
};
let response = pink.httpRequest({
url: httpUrl,
method: "GET",
headers,
returnTextBody: true,
});
let respBody = response.body;
if (typeof respBody !== "string") {
throw Error.FailedToDecode;
}
console.log(respBody);
return JSON.parse(respBody);
}
- Based on the weather condition results, the program selects an appropriate image based on the symbol returned from the wttr.in API.
const WEATHER_SYMBOL_PLAIN = {
"?": "https://ipfs.apillon.io/ipfs/QmV5apTs4snioxEFj5YmhipNpLgjGNk6hcgJj5vEcK7oxv",
"mm": "https://ipfs.apillon.io/ipfs/QmNYBmQKvfEnAnGRbhTdZ8XxAp9BDpvCnWqS1VkKTzFy1S",
"=": "https://ipfs.apillon.io/ipfs/QmNYBmQKvfEnAnGRbhTdZ8XxAp9BDpvCnWqS1VkKTzFy1S",
"///": "https://ipfs.apillon.io/ipfs/QmVUDR7JosCXj2XXBax6rTiJu42vhSv3HSMNaj458ogxxN",
"//": "https://ipfs.apillon.io/ipfs/QmVUDR7JosCXj2XXBax6rTiJu42vhSv3HSMNaj458ogxxN",
"**": "https://ipfs.apillon.io/ipfs/QmbofYJo4PyVfckuqQAiPnBRsZ1mL5yo7SfFH8c1TqwhGH",
"*/*": "https://ipfs.apillon.io/ipfs/QmbofYJo4PyVfckuqQAiPnBRsZ1mL5yo7SfFH8c1TqwhGH",
"/": "https://ipfs.apillon.io/ipfs/QmVUDR7JosCXj2XXBax6rTiJu42vhSv3HSMNaj458ogxxN",
".": "https://ipfs.apillon.io/ipfs/QmVUDR7JosCXj2XXBax6rTiJu42vhSv3HSMNaj458ogxxN",
"x": "https://ipfs.apillon.io/ipfs/QmVUDR7JosCXj2XXBax6rTiJu42vhSv3HSMNaj458ogxxN",
"x/": "https://ipfs.apillon.io/ipfs/QmbofYJo4PyVfckuqQAiPnBRsZ1mL5yo7SfFH8c1TqwhGH",
"*": "https://ipfs.apillon.io/ipfs/QmbofYJo4PyVfckuqQAiPnBRsZ1mL5yo7SfFH8c1TqwhGH",
"*/": "https://ipfs.apillon.io/ipfs/QmbofYJo4PyVfckuqQAiPnBRsZ1mL5yo7SfFH8c1TqwhGH",
"m": "https://ipfs.apillon.io/ipfs/QmV5apTs4snioxEFj5YmhipNpLgjGNk6hcgJj5vEcK7oxv",
"o": "https://ipfs.apillon.io/ipfs/QmV5apTs4snioxEFj5YmhipNpLgjGNk6hcgJj5vEcK7oxv",
"/!/": "https://ipfs.apillon.io/ipfs/QmVUDR7JosCXj2XXBax6rTiJu42vhSv3HSMNaj458ogxxN",
"!/": "https://ipfs.apillon.io/ipfs/QmVUDR7JosCXj2XXBax6rTiJu42vhSv3HSMNaj458ogxxN",
"*!*": "https://ipfs.apillon.io/ipfs/QmbofYJo4PyVfckuqQAiPnBRsZ1mL5yo7SfFH8c1TqwhGH",
"mmm": "https://ipfs.apillon.io/ipfs/QmNYBmQKvfEnAnGRbhTdZ8XxAp9BDpvCnWqS1VkKTzFy1S",
}
- This image is then set as the new image for your NFT, effectively updating it to reflect the current weather conditions. This information is formatted in JSON and updated in the 4Everland S3 bucket for the selected city. To update the S3 bucket, the Phat Contract needs to
invokeContract()
to the S3 Storage Rust SDK Phat Contract.
function updateS3Storage(city: string, metadata: string) {
console.log(metadata)
let uint8Array = new Uint8Array(metadata.length);
for (let i = 0; i < metadata.length; i++) {
uint8Array[i] = metadata.charCodeAt(i);
}
const endpoint = 'endpoint.4everland.co'
const region = 'us-west-1'
const bucket = 'wrlx-bucket'
const object_key = `${city}/weather`;
const value = uint8Array;
const bytes = WalkerImpl.encode([
endpoint,
region,
bucket,
object_key,
Array.from(value)
], encodeS3Put)
const decoded = WalkerImpl.decode(bytes, decodeS3Put)
console.info(`input decoded: ${decoded}`)
console.log(bytes);
const delegateOutput = pink.invokeContract({
callee:
"0x6295f7139ce955037419c444341f29e5ccc7a1e2165d6ca8591a4c401fb37abe",
selector: 0xa2cb64e1,
input: bytes,
});
console.log(`output: ${delegateOutput}`);
}
Step 4: Dynamic Changes to the NFT
If the weather conditions change and an NFT owner requests to update the weather, the Phat Contract will go through similar process as step 3 and update the NFT metadata accordingly. This means that your NFT is dynamically changed and always reflects the latest weather conditions.
- When the weather conditions change, the off-chain program triggers an update to the NFT.
- The new weather conditions are checked, and a new image is selected along with the NFT metadata formatted in JSON to be updated in the 4Everland S3 bucket.
- The NFT is updated with the new image, reflecting the change in weather conditions.
Closing
Voila! You now own an NFT that updates its image with your city's weather. This special touch enhances its interactivity, setting your NFT apart as a a truly one-of-a-kind digital asset.
Happy Minting!
This video will take you through the full developer journey.
- First we request to mint a new weather NFT, but this fails due to the MojosWeather NFT Collection not having permissions granted to the Consumer Contract as a Minter.
- We grant the Consumer Contract as a Minter role and request a mint of a MojosWeather NFT with the city defined as
MexicoCity
.
- The NFT is minted to the requester and we can see the NFT appear with the latest weather information for Mexico City.
- Lucky for us, the next day had a change in weather and when we request to update the weather for Mexico City, the NFT metadata is dynamically updated with the new image and data.