arrow_back

Creating Generative Art Dynamic NFTs with Google Cloud Vertex AI and Chainlink Functions

Sign in Join
Test and share your knowledge with our community!
done
Get access to over 700 hands-on labs, skill badges, and courses

Creating Generative Art Dynamic NFTs with Google Cloud Vertex AI and Chainlink Functions

Lab 1 hour 30 minutes universal_currency_alt 3 Credits show_chart Intermediate
Test and share your knowledge with our community!
done
Get access to over 700 hands-on labs, skill badges, and courses

GSP1179

Google Cloud Self-Paced Labs

Overview

Chainlink Functions provides your smart contracts with access to a trust-minimized compute infrastructure. Your smart contract sends your code to a Decentralized Oracle Network (DON), and each DON's oracle runs the same code in a serverless environment. The DON aggregates all the independent runs and returns the final result to your smart contract. Your code can be anything from simple computation to fetching data from API providers.

Vertex AI is a machine learning (ML) platform from Google Cloud that lets you train and deploy ML models and AI applications. Vertex AI combines data engineering, data science, and ML engineering workflows, enabling your teams to collaborate using a common toolset. Learn more.

In this lab you'll learn the process of creating a Generative ART dynamic NFTs using Google Cloud Vertex AI and Chainlink functions.

scenario flow chart

Objectives

In this lab, you will learn how to:

  • Use Vertex AI API to generate images using text prompt
  • Build and deploy cloud functions
  • Set up the Google Cloud Storage buckets
  • Deploy NFT smart contract
  • Integrate the Cloud Functions API with Chainlink Functions

Prerequisites

  • Visual Studio Code or any other code editor
  • The Metamask Plugin installed and configured in your Chrome browser other Ethereum Web3 wallet.

Install and configure your Web3 wallet for Polygon Mumbai

  1. Install the MetaMask wallet or other Ethereum Web3 wallet.
  2. Set the network for your wallet to the Polygon Mumbai testnet. If you need to add Mumbai to your wallet, you can find the chain ID and the LINK token contract address on the Mumbai Testnet Chainlist or LINK Token Contracts page.
  3. Request testnet MATIC from the Polygon Faucet.
  4. Request testnet LINK from Chainlink Faucet.

Setup and requirements

Before you click the Start Lab button

Read these instructions. Labs are timed and you cannot pause them. The timer, which starts when you click Start Lab, shows how long Google Cloud resources will be made available to you.

This Qwiklabs hands-on lab lets you do the lab activities yourself in a real cloud environment, not in a simulation or demo environment. It does so by giving you new, temporary credentials that you use to sign in and access Google Cloud for the duration of the lab.

What you need

To complete this lab, you need:

  • Access to a standard internet browser (Chrome browser recommended).
  • Time to complete the lab.

Note: If you already have your own personal Google Cloud account or project, do not use it for this lab.

Note: If you are using a Pixelbook, open an Incognito window to run this lab.

How to start your lab and sign in to the Google Cloud Console

  1. Click the Start Lab button. If you need to pay for the lab, a pop-up opens for you to select your payment method. On the left is a panel populated with the temporary credentials that you must use for this lab.

    Open Google Console

  2. Copy the username, and then click Open Google Console. The lab spins up resources, and then opens another tab that shows the Sign in page.

    Sign in

    Tip: Open the tabs in separate windows, side-by-side.

  3. In the Sign in page, paste the username that you copied from the Connection Details panel. Then copy and paste the password.

    Important: You must use the credentials from the Connection Details panel. Do not use your Qwiklabs credentials. If you have your own Google Cloud account, do not use it for this lab (avoids incurring charges).

  4. Click through the subsequent pages:

    • Accept the terms and conditions.
    • Do not add recovery options or two-factor authentication (because this is a temporary account).
    • Do not sign up for free trials.

After a few moments, the Cloud Console opens in this tab.

Activate Cloud Shell

Cloud Shell is a virtual machine that is loaded with development tools. It offers a persistent 5GB home directory and runs on the Google Cloud. Cloud Shell provides command-line access to your Google Cloud resources.

In the Cloud Console, in the top right toolbar, click the Activate Cloud Shell button.

Cloud Shell icon

Click Continue.

cloudshell_continue.png

It takes a few moments to provision and connect to the environment. When you are connected, you are already authenticated, and the project is set to your PROJECT_ID. For example:

Cloud Shell Terminal

gcloud is the command-line tool for Google Cloud. It comes pre-installed on Cloud Shell and supports tab-completion.

You can list the active account name with this command:

gcloud auth list

(Output)

Credentialed accounts: - <myaccount>@<mydomain>.com (active)

(Example output)

Credentialed accounts: - google1623327_student@qwiklabs.net

You can list the project ID with this command:

gcloud config list project

(Output)

[core] project = <project_ID>

(Example output)

[core] project = qwiklabs-gcp-44776a13dea667a6

Enable Vertex AI API

Navigate to Vertex AI API. From the Navigation menu, click More Products, then click on Vertex AI. Click Enable all recommended APIs.

Scenario

You are developing an NFT campaign featuring popular monuments with underlying utilities. To create unique digital artworks, you intend to leverage the capabilities of Generative AI. To gamify the campaign, you also intend to make the NFTs dynamic, meaning that their attributes and images can be updated based on predefined rules and real-world data.

Task 1. Create Cloud Storage Bbckets

Cloud Storage allows world-wide storage and retrieval of any amount of data at any time. You can use Cloud Storage for a range of scenarios including serving website content, storing data for archival and disaster recovery, or distributing large data objects to users via direct download.

Buckets are the basic containers that hold your data in Cloud Storage.

In this step you will create a Cloud Storage Bucket which will be used to store NFTs metadata and images.

To create a bucket:

  1. From the Navigation menu ( Navigation Menu icon) > Cloud Storage > Buckets.

  2. Click Create.

  3. Enter your bucket information and click Continue to complete each step. Name your bucket: Enter a unique name for your bucket. For this lab, use your as the bucket name because it will always be unique.

  4. Leave the rest of the fields as their default values and click Create.

Note: If you are prompted with Public access will be prevented, uncheck Enforce public access prevention on this bucket and click Confirm.

Click Check my progress to verify the objective. Enable Vertex AI API and create cloud storage bucket

Task 2. Upload SmartCon logo into the Bucket

SmartCon 2023 logo

In this scenario, use the Google Cloud Function to compose a watermark onto the image generated by Vertex AI. The above image will be used as the watermark.

To upload the above image into your new bucket:

  1. Right-click on the image above and download it to your computer. Save the image as logo.png, renaming it on download.
  2. In the Cloud Storage browser page, click the name of the bucket that you created.
  3. In the Objects tab, click Upload files.
  4. In the file dialog, go to the file that you downloaded and select it.
  5. Ensure the file is named logo.png. If it is not, click the three dot icon for your file, select Rename from the dropdown, and rename the file to logo.png.

After the upload completes, you should see the file name and information about the file, such as its size and type.

Click Check my progress to verify the objective. Upload SmartCon logo into the Bucket

Task 3. Share a bucket publicly

To allow public access to the bucket and create a publicly accessible URL for the image:

  1. Click the Permissions tab above the list of files.
  2. Ensure the view is set to Principals. Click Grant Access to view the Add principals pane.
  3. In the New principals box, enter allUsers.
  4. In the Select a role drop-down, select Cloud Storage > Storage Object Viewer.
  5. Click Save.
  6. In the Are you sure you want to make this resource public? window, click Allow public access.
Add principlas page allUsers with Storage Object Viewer role
  1. To verify, click the Objects tab to return to the list of objects. Your object's Public access column should read Public to internet.
Note: If your object does not appear to be public after following the previous steps, you may need to refresh your browser page.
  1. Press the Copy URL button for your object and paste it into a separate tab to view your image.

The Copy URL button provides a shareable URL similar to the following:

https://storage.googleapis.com//logo.png

Click Check my progress to verify the objective. Share the bucket publicly

Task 4. Create Cloud Function

A Cloud Function is a piece of code that runs in response to an event, such as an HTTP request, a message from a messaging service, or a file upload. Cloud events are things that happen in your cloud environment. These might be things like changes to data in a database, files added to a storage system, or a new virtual machine instance being created.

Since Cloud Functions are event-driven, they only run when something happens. This makes them a good choice for tasks that need to be done quickly or that don't need to be running all the time.

In this step, you're going to create a Cloud Function using the console.This function has the following functionalities:

  • Generate the prompt from predefined set of attributes.
  • Use the Generated prompt to generate Image using Vertex AI Imagen model.
  • Compose the watermark logo on the image.
  • Upload the image and metadata to Cloud Storage bucket.

To create the function, follow the below steps.

  1. From the Navigation menu ( Navigation Menu icon) > Cloud Functions.

  2. Click Create function.

  3. Enter the following values:

Field Value
Environment 2nd gen
Function name GenerateNFT
Region Leave Default
Trigger Select Allow unauthenticated invocations
Memory allocated (In Runtime, Build, Connections and Security Settings) Keep it default and click Next
Note: If you are prompted with Enable required APIs, click ENABLE.

Task 5. Deploy the function

Still in the Create function dialog, in Source code for Inline editor replace the default code with the following implementation for index.js & package.json:

  1. In package.json add the following:
{ "dependencies": { "@google-cloud/functions-framework": "^3.0.0", "@google-cloud/storage": "^7.0.1", "google-auth-library": "^9.0.0", "jimp": "^0.22.10" } }
  1. In Index.js add your your Bucket name and Project Id, and :
const functions = require('@google-cloud/functions-framework'); const { GoogleAuth } = require('google-auth-library'); const { Storage } = require('@google-cloud/storage') const Jimp = require('jimp') ; const storage = new Storage(); const modelId = "imagegeneration" const apiEndpoint = "us-central1-aiplatform.googleapis.com" const bucketName = "{{{ project_0.project_id|BUCKET NAME}}}"; const projectId = "{{{ project_0.project_id|PROJECT ID}}}" const overlayIcon = "https://storage.googleapis.com/"+ bucketName+ "/logo.png" function getRandomInt(max) { return Math.floor(Math.random() * max); } async function generatePrompt(tokenId, seed, isUpdate) { const traitsLib = new Map(); traitsLib.set("primaryObject",["Great Wall of China", "Leaning Tower of Pisa", "Pyramid of Giza","Sydney Opera House", "Eiffel Tower", "Statue of Liberty", "Taj Mahal", "Easter Island Moai", "Machu Picchu" ]) traitsLib.set("style",["Graffiti art", "floating","dot shading", "Indoors macro photography", "watercolor", "sketch", "cyberpunk", "Pop Art", "Digital Art", "Landscape"]) traitsLib.set("Shape" , ["ambient shapes", "blobby", "hyperreal isometric miniature","splatters shapes"]) traitsLib.set("SecondaryObject" , ["closeup fur","sculpture","closeup leather", "smooth flowing structured fabric", "iridescent glass" ]) traitsLib.set("lighting" , ["warm", "soft lighting", "chiaroscuro light", "chiaroscuro light and shadow"]) traitsLib.set("sharpness" , ["blurry", "sharp"]) traitsLib.set("background" , ["scenic", "Abstract Background"]) traitsLib.set("colorScheme" , ["pastel colors", "cream palette", "soft blue and green palette","black and white","heavily diluted watercolor","vivid and bold colors", "green and blue watercolor","Pastel Petal Pink"]) let prompt = "" let attributes = [] if(isUpdate == false) { for (const [trait, values] of traitsLib) { console.log(`${trait}`) let rno = getRandomInt(values.length) if (prompt != ""){ prompt = prompt + "," + values[rno] } else { prompt = values[rno] } attributes.push({"trait_type" : trait, "value" : values[rno]}) } attributes.push({"trait_type" : "seed", "value" : seed}) } else { const tokenMetadata = await storage.bucket(bucketName).file(tokenId + ".json").download() const jsonContent = JSON.parse(tokenMetadata) prompt = jsonContent["description"] attributes = jsonContent["attributes"] for (let i = 0; i < attributes.length; i++) { if(attributes[i]["trait_type"] == "seed") { attributes[i]["value"] = seed continue } } } return {prompt,attributes} } async function imageOverlay(origanlImage) { // Function name is same as of file // Reading watermark Image let watermark = await Jimp.read(overlayIcon); //watermark = watermark.resize(150,150); // Resizing watermark image // // Reading original image const imageBuffer = Buffer.from(origanlImage, "base64") let image = await Jimp.read(imageBuffer); //image = await image.resize(900,900); watermark = await watermark image.composite(watermark, 30, 30, { mode: Jimp.BLEND_SOURCE_OVER, opacityDest: 1, opacitySource: 1 }) return await image.getBufferAsync(Jimp.MIME_PNG) } async function storeFile(buffer, tokenId, description, attributes) { const bucket = storage.bucket(bucketName); const file = bucket.file(tokenId + "_img.png"); tokenMetadata = {} tokenMetadata["description"] = description tokenMetadata["attributes"] = attributes const stream = file.createWriteStream({ metadata: { contentType: 'image/png', }, }); stream.on('error', (err) => { console.error(err); }); stream.on('finish', () => { console.log(`Image uploaded to ${tokenId}.`); }); stream.end(buffer); tokenMetadata["image"] = "https://storage.cloud.google.com/" + bucketName + "/" + tokenId + "_img.png" await bucket.file(tokenId + ".json").save(JSON.stringify(tokenMetadata)) return "https://storage.cloud.google.com/" + bucketName + "/" + tokenId + ".json" } async function sendRequest(options) { const auth = new GoogleAuth({ scopes: "https://www.googleapis.com/auth/cloud-platform", }); const client = await auth.getClient(); const accessToken = (await client.getAccessToken()).token; const data = { instances: options.instances, parameters: options.parameters }; const response = await fetch( `https://${options.apiEndpoint}/v1/projects/${options.projectId}/locations/us-central1/publishers/google/models/${options.modelId}:predict`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify(data) } ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } functions.http('helloHttp',async (req, res) => { console.log("Incoming Request") tokenId = req.body["tokenId"] seed = req.body["seed"] isUpdate = req.body["isUpdate"] let {prompt, attributes} = await generatePrompt(tokenId, seed, isUpdate) console.log(prompt) const params = { apiEndpoint: apiEndpoint, projectId: projectId, modelId: modelId, "instances": [ { "prompt": prompt, } ], "parameters": { "sampleCount": 1, "seed": seed } }; sendRequest(params) .then(async (response) => { overlayImage = await imageOverlay(response["predictions"][0]["bytesBase64Encoded"]) tokenUrl = await storeFile(overlayImage, tokenId, prompt, attributes) res.send({"tokenUrl": tokenUrl}) }) .catch((error) => { console.error(error); res .status(500) .json({ message: "Invalid Input - " + error }) }); });
  1. At the bottom, click Deploy to deploy the function.
  2. After you click Deploy, the console redirects to the Cloud Functions Overview page.

While the function is being deployed, the icon next to it is a small spinner. When it's deployed, the spinner is a green check mark.

GenerateNFT page with green check mark

Click Check my progress to verify the objective. Create and Deploy the Cloud function

Task 6. Test the function

  1. In the Cloud Functions Overview page, got to Testing tab.

  2. In the Configure Triggering event field, enter the following json object and click Test the function.

{ "tokenId": 21, "seed": 132141, "isUpdate": false }

In the Output field, you should see the Token URI similar to below JSON:

{ "tokenUrl":"https://storage.cloud.google.com/deepchenna-chainlink-dnft-demo/21.json" } Note: If you are not getting the above output try to refresh the console and enter the above json object.

In the Logs field, a status code of 200 indicates success. (It may take a minute for the logs to appear.)

successful logs

Click Check my progress to verify the objective. Test the Cloud Function

Task 7. Make the Function publicly invokable

Follow the below steps to make the Functions API publicly callable without any authentication.

  1. In the Cloud Functions Overview page click the Permissions tab.
  2. Ensure the view is set to Principals. Click Grant Access to view the Add principals pane.
  3. In the New principals box, enter allUsers.
  4. In the Select a role drop-down, select Cloud Functions > Cloud Functions Invoker.
  5. Click Save.
  6. In the Are you sure you want to make this resource public? window, click Allow public access.
  7. Now copy the Function URL from the function overview page
Note: For later use in tasks, please keep the Function URL (Copy from the function overview page) and Base URL for Token metadata (https://storage.googleapis.com/BUCKET_NAME/)

Click Check my progress to verify the objective. Make the cloud function publicly invokable

Task 8. Chainlink Functions

Install the required frameworks and dependencies

  1. Run below commands to clone this repo and change directories:
git clone https://github.com/smartcontractkit/functions-google-vertex-ai.git && \cd functions-google-vertex-ai/
  1. You can use gcloud provided latest version of Node.js or Install Node.js 18. Optionally, you can use the nvm package to switch between Node.js versions with nvm use 18.

To ensure you are running the correct version in a terminal, run:

node -v
  1. Install DENO:
curl -fsSL https://deno.land/x/install/install.sh | sh export PATH="$HOME/.deno/bin:$PATH"
  1. Now check your version to confirm the install:
deno --version

Optional: If you encounter problems, you may need to run:

source ~/.bashrc # or ~/.zshrc, or the appropriate configuration file
  1. Run npm install to install the dependencies.
npm install
  1. For higher security, the starter kit encrypts your environment variables at rest. Note:

    Set an encryption password for your environment variables.

npx env-enc set-pw
  1. To configure your environment variables that you need to send your requests to the Polygon Mumbai network, run:
npx env-enc set
  1. You will be asked for a variable name, enter:
POLYGON_MUMBAI_RPC_URL
  1. Set a URL for the Polygon Mumbai testnet, You can sign up for a personal endpoint from Alchemy, Infura, or another node provider service. Enter:
<RPC_URL>
  1. You will then be asked if you want to add another variable name, enter:
PRIVATE_KEY
  1. Find the private key for your testnet wallet. If you use MetaMask, follow the instructions to Export a Private Key. Then enter:
<YOUR_PRIVATE_KEY>

The Chainlink Functions hardhat starter kit uses your private key to sign any transactions you make such as deploying your consumer contract, creating subscriptions, and making requests.

Note: If you get into env-enc package issues you can fallback to dotenv, below are the steps to use dotenv instead of env-enc. Create a .env file that contains the 2 environment variables private key and mumbai RPC URL: npm install dotenv --save When complete, replace require("@chainlink/env-enc").config(); with the below line in these JS files request.js, secrets.js and env.js: require('dotenv').config()

Task 9. Configure your on-chain resources

After you configure your local environment, configure some on-chain resources to process your requests using Chainlink Functions.

  1. Open the Functions Consumer NFT contract in Remix.

  2. Compile the contract by clicking the "Compile" button in the Solidity Compiler tab.

Solidity compiler Note: If you get any compilation issues in Solidity Compiler, make sure that you have selected compiler version 0.8.19+commit.7dd6d404
  1. Open MetaMask and select the Polygon Mumbai network.
1cc73ddf0c42e031.png
  1. In Remix under the Deploy & Run Transactions tab, select Injected Provider - MetaMask in the Environment list. Remix will use the MetaMask wallet to communicate with Polygon Mumbai testnet.
MetaMask screen with Mumbai network selected
  1. Under the Deploy section, fill in the router address. For Polygon Mumbai, the router address is:
0x6E2dc0F9DB014aE19888F539E59285D2Ea04244C
  1. Under the Deploy section, put in your Cloud Storage bucket URL for base URL.
https://storage.cloud.google.com/{{{ project_0.project_id|BUCKET NAME}}}/
  1. Click the Deploy button to deploy the contract. MetaMask prompts you to confirm the transaction. Check the transaction details to make sure you are deploying the contract to Polygon Mumbai.

Task 10. Mint your NFT

  1. Expand the contract you just deployed under Deployed Contracts.

  2. Paste your wallet address into the safeMint field and hit the labeled orange button to call the function.

  3. MetaMask prompts you to confirm the transaction.

  4. After you confirm the transaction, get your contract address that appears in the Deployed Contracts list. Copy the contract address for the next steps.

Task 11. Chainlink Functions: Create a subscription

  1. Follow the Managing Functions Subscriptions guide to accept the Chainlink Functions Terms of Service (ToS), create a subscription, fund it, then add your consumer contract address to it.

  2. You can find the Chainlink Functions UI at https://functions.chain.link/.

Task 12. Chainlink Functions: Generate NFT Metadata

  1. Click the Open Editor button in Cloud Shell and open file request.js, which is located in the /functions-google-vertex-ai/examples/POST-data/request.js folder.

  2. Replace the consumer contract address and the subscription ID with your own values:

const consumerAddress = "0x8dFf78B7EE3128D00E90611FBeD20A71397064D9" // REPLACE this with your Functions consumer address const subscriptionId = 3 // REPLACE this with your subscription ID and save the file.
  1. Open the file source.js, which is located in the /functions-google-vertex-ai/examples/POST-data/source.js folder:
const seed = 999999; // REPLACE this with a number between 100000 - 999999 of your choosing. This is your seed for Google Cloud Vertex AI. const url = "<GCP-FUNCTION-URL>"; // REPLACE this with your API endpoint from Task 8, Step 7 above and save the file.
  1. Run the below command in Cloud Shell to make a request:
node ./examples/POST-data/request.js The script runs your function in a sandbox environment before making an on-chain transaction.
  1. You can now navigate to your tokenUrl below:

    tokenUrl: https://storage.cloud.google.com//0.json

  2. At the end of this JSON metadata will be your NFT:

    image: https://storage.cloud.google.com//0_img.png

    • Open this URL to see your NFT!

Click Check my progress to verify the objective. Chainlink Functions: Generate NFT Metadata

Task 13. Chainlink Functions: Update NFT Metadata

  1. In Cloud Shell, open file source.js, which is located in the /functions-google-vertex-ai/examples/POST-data/source.js folder.
const seed = <NEW-SEED>; // REPLACE this with a different number between 100000 - 999999 of your choosing. const isUpdate = false; // REPLACE this with true. This specifies we are updating the metadata, not updating existing metadata.
  1. Run the below command to make a request.
node ./examples/POST-data/request.js

[Similar output as above]

  1. You can now navigate to your token metadata below:

    Token metadata: https://storage.cloud.google.com//0.json

  2. At the end of this JSON metadata will be your updated NFT:

    image: https://storage.cloud.google.com//0_img.png

    • Open this URL to see your NFT updated with a new seed!

Click Check my progress to verify the objective. Chainlink Functions: Update NFT Metadata

Congratulations!

You're ready to create your own NFT Collection from a few simple prompt attributes using Google Cloud Vertex AI and Chainlink Functions.

Next steps

Google Cloud Training & Certification

...helps you make the most of Google Cloud technologies. Our classes include technical skills and best practices to help you get up to speed quickly and continue your learning journey. We offer fundamental to advanced level training, with on-demand, live, and virtual options to suit your busy schedule. Certifications help you validate and prove your skill and expertise in Google Cloud technologies.

Manual Last Updated Date: November 23, 2023

Lab Last Tested Date: November 23, 2023

Copyright 2022 Google LLC All rights reserved. Google and the Google logo are trademarks of Google LLC. All other company and product names may be trademarks of the respective companies with which they are associated.