Protect DALL·E AI-Generated Images

Learn how to license and protect DALL·E AI-Generated images on Story.

In this tutorial, you will learn how to license and protect DALL·E 2 AI-Generated images by registering it on Story.

The Explanation

Let's say you generate an image using AI. Without adding a proper license to your image, others could use it freely. In this tutorial, you will learn how to add a license to your DALL·E 2 AI-Generated image so that if others want to use it, they must properly license it from you.

In order to register that IP on Story, you first need to mint an NFT to represent that IP, and then register that NFT on Story, turning it into an 🧩 IP Asset.

0. Before you Start

There are a few steps you have to complete before you can start the tutorial.

  1. Add your Story Network Testnet wallet's private key to .env file:
WALLET_PRIVATE_KEY=
  1. Go to Pinata and create a new API key. Add the JWT to your .env file:
PINATA_JWT=
  1. Go to OpenAI and create a new API key. Add the new key to your .env file:

🚧

OpenAI Credits

In order to generate an image, you'll need OpenAI credits. If you just created an account, you will probably have a free trial that will give you a few credits to start with.

OPENAI_API_KEY=
  1. Add your preferred RPC URL to your .env file. You can just use the public default one we provide:
RPC_PROVIDER_URL=https://rpc.odyssey.storyrpc.io
  1. Install the dependencies:
npm install @story-protocol/core-sdk pinata-web3 viem openai

1. Generate an Image

In a main.ts file, add the following code to generate an image:

import OpenAI from 'openai'

const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
})

const image = await openai.images.generate({ 
  model: 'dall-e-2', 
  prompt: 'A cute baby sea otter' 
});

console.log(image.data[0].url) // the url to the newly created image

2. Set up your Story Config

In a utils.ts file, add the following code to set up your Story Config:

import { StoryClient, StoryConfig } from "@story-protocol/core-sdk";
import { http } from "viem";
import { privateKeyToAccount, Address, Account } from "viem/accounts";

const privateKey: Address = `0x${process.env.WALLET_PRIVATE_KEY}`;
export const account: Account = privateKeyToAccount(privateKey);

const config: StoryConfig = {
  account: account,
  transport: http(process.env.RPC_PROVIDER_URL),
  chainId: "odyssey",
};
export const client = StoryClient.newClient(config);

3. Set up your IP Metadata

View the IPA Metadata Standard and construct your metadata for your IP. Back in the main.ts file, use the generateIpMetadata function to properly format your metadata and ensure it is of the correct type, as shown below:

import OpenAI from 'openai'
import { IpMetadata } from "@story-protocol/core-sdk";
import { client, account } from './utils'

async function main() {
  // previous code here ...

  const ipMetadata: IpMetadata = client.ipAsset.generateIpMetadata({
    title: 'Dall-E 2 Image',
    description: 'An image generated by Dall-E 2',
    ipType: 'image',
    attributes: [
      {
        key: 'Model',
        value: 'dall-e-2',
      },
      {
        key: 'Prompt',
        value: 'A cute baby sea otter',
      },
    ],
    creators: [
      {
        name: 'Jacob Tucker',
        contributionPercent: 100,
        address: account.address,
      },
    ],
  }) 
}

main();

4. Set up your NFT Metadata

The NFT Metadata follows the ERC-721 Metadata Standard.

import OpenAI from 'openai'
import { IpMetadata } from "@story-protocol/core-sdk";
import { client, account } from './utils'

async function main() {
  // previous code here ...

  const nftMetadata = {
    name: 'Image Ownership NFT',
    description: 'This NFT represents ownership of the image generated by Dall-E 2',
    image: image.data[0].url,
    attributes: [
      {
        key: 'Model',
        value: 'dall-e-2',
      },
      {
        key: 'Prompt',
        value: 'A cute baby sea otter',
      },
    ],
  } 
}

main();

5. Upload your IP and NFT Metadata to IPFS

In a separate uploadToIpfs.ts file, create a function to upload your IP & NFT Metadata objects to IPFS:

import { PinataSDK } from "pinata-web3";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT,
});

export async function uploadJSONToIPFS(jsonMetadata: any): Promise<string> {
  const { IpfsHash } = await pinata.upload.json(jsonMetadata);
  return IpfsHash;
}

You can then use that function to upload your metadata, as shown below:

import OpenAI from 'openai'
import { IpMetadata } from "@story-protocol/core-sdk";
import { client, account } from './utils'
import { uploadJSONToIPFS } from "./utils/uploadToIpfs";
import { createHash } from "crypto";

async function main() {
  // previous code here...

  const ipIpfsHash = await uploadJSONToIPFS(ipMetadata);
  const ipHash = createHash("sha256")
    .update(JSON.stringify(ipMetadata))
    .digest("hex");
  const nftIpfsHash = await uploadJSONToIPFS(nftMetadata);
  const nftHash = createHash("sha256")
    .update(JSON.stringify(nftMetadata))
    .digest("hex");
}

main();

6. Register the NFT as an IP Asset

In this step, we will use the 📦 SPG to combine minting and registering our NFT into one transaction call.

First, in a separate file createSpgNftCollection.ts, you must create a new SPG NFT collection. You can do this with the SDK (view a working example here):

Why do we have to do this?

In order to use the mintAndRegisterIpAssetWithPilTerms function below, we'll have to deploy an SPG NFT collection so that the SPG can do the minting for us.

Instead of doing this, you could technically write your own contract that implements ISPGNFT. But an easy way to create a collection that implements ISPGNFT is just to call the createCollection function in the SPG contract using the SDK, as shown below.

import { zeroAddress } from 'viem'
import { client } from './utils'

async function main() {
  // Create a new SPG NFT collection
  //
  // NOTE: Use this code to create a new SPG NFT collection. You can then use the
  // `newCollection.spgNftContract` address as the `spgNftContract` argument in
  // functions like `mintAndRegisterIpAssetWithPilTerms`, which you'll see later.
  //
  // You will mostly only have to do this once. Once you get your nft contract address,
  // you can use it in SPG functions.
  //
  const newCollection = await client.nftClient.createNFTCollection({
    name: 'Test NFT',
    symbol: 'TEST',
    isPublicMinting: true,
    mintOpen: true,
    mintFeeRecipient: zeroAddress,
    contractURI: '',
    txOptions: { waitForTransaction: true },
  })

  console.log(`New SPG NFT collection created at transaction hash ${newCollection.txHash}`)
  console.log(`NFT contract address: ${newCollection.spgNftContract}`)
}

main();

Run this file and look at the console output. Copy the SPG NFT contract address and add that value as SPG_NFT_CONTRACT_ADDRESS to your .env file:

SPG_NFT_CONTRACT_ADDRESS=

📘

Note

You only have to do the above step once. Once you have your SPG NFT contract address, you can register any amount of IPs and will not have to do this again.

The code below will mint an NFT, register it as an 🧩 IP Asset, set License Terms on the IP, and then set both NFT & IP metadata.

import OpenAI from 'openai'
import { IpMetadata } from "@story-protocol/core-sdk";
import { client, account } from './utils'
import { uploadJSONToIPFS } from "./utils/uploadToIpfs";
import { createHash } from "crypto";
import { Address } from "viem";

async function main() {
  // previous code here ...

  const response = await client.ipAsset.mintAndRegisterIpAssetWithPilTerms({
    spgNftContract: process.env.SPG_NFT_CONTRACT_ADDRESS as Address,
    terms: [], // IP already has non-commercial social remixing terms. You can add more here.
    ipMetadata: {
      ipMetadataURI: `https://ipfs.io/ipfs/${ipIpfsHash}`,
      ipMetadataHash: `0x${ipHash}`,
      nftMetadataURI: `https://ipfs.io/ipfs/${nftIpfsHash}`,
      nftMetadataHash: `0x${nftHash}`,
    },
    txOptions: { waitForTransaction: true },
  });

  console.log(
    `Root IPA created at transaction hash ${response.txHash}, IPA ID: ${response.ipId}`
  );
  console.log(
    `View on the explorer: https://explorer.story.foundation/ipa/${response.ipId}`
  ); 
}

main();

7. Done!