How to Add Metadata to an IP Asset with the SDK

Learn how to add IP & NFT metadata to an IP Asset using the Typescript SDK.

👍

See the Completed Code

To see a completed, working example of how to add metadata to your IP Asset, please see the metadata tutorial.

Note: The completed code follows step 5b. If you want to follow step 5a instead, you can very easily swap out the code.

📺

Video Walkthrough

If you want to check out a video walkthrough of this tutorial, go here.

In this tutorial, you will learn how to register your IP on Story using the TypeScript SDK.

The Explanation

Let's say you have some off-chain IP (ex. a book, a character, a drawing, etc). 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. As you can probably tell, this is two transactions: Mint NFT ▶️ Register NFT

To make things easier, we provide a function that combines these two operations called mintAndRegisterIpAssetWithPilTerms, which you will use below. This function not only mints an NFT and registers it, but also allows you to set License Terms on the IP and set metadata on the NFT & the IP such that it displays properly on things like our explorer.

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=<YOUR_WALLET_PRIVATE_KEY>
  1. Go to Pinata and create a new API key. Add the JWT to your .env file:
PINATA_JWT=<YOUR_PINATA_JWT>
  1. Add your preferred RPC URL to your .env file. You can just use the public default one we provide:
RPC_PROVIDER_URL=https://testnet.storyrpc.io
  1. Install the dependencies:
npm install @story-protocol/core-sdk @pinata/sdk viem

1. 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}`
const account: Account = privateKeyToAccount(privateKey)

const config: StoryConfig = {  
  account: account,  
  transport: http(process.env.RPC_PROVIDER_URL),  
  chainId: 'iliad',  
}  
const client = StoryClient.newClient(config)

2. Set up your IP Metadata

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

import { IpMetadata } from '@story-protocol/core-sdk'

const ipMetadata: IpMetadata = client.ipAsset.generateIpMetadata({
  title: 'My IP Asset',
  description: 'This is a test IP asset',
  watermarkImg: 'https://picsum.photos/200',
  attributes: [
    {
      key: 'Rarity',
      value: 'Legendary',
    },
  ],
})

3. Set up your NFT Metadata

The NFT Metadata follows the ERC-721 Metadata Standard.

const nftMetadata = {
  name: 'Test NFT',
  description: 'This is a test NFT',
  image: 'https://picsum.photos/200',
}

4. Upload your IP and NFT Metadata to IPFS

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

const pinataSDK = require('@pinata/sdk')

export async function uploadJSONToIPFS(jsonMetadata): Promise<string> {
    const pinata = new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT })
    const { IpfsHash } = await pinata.pinJSONToIPFS(jsonMetadata)
    return IpfsHash
}

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

import { uploadJSONToIPFS } from './utils/uploadToIpfs'
import { createHash } from 'crypto'

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')

5. Register the NFT as an IP Asset

There are two ways to do this step. Either:

  1. 5a. You already have an NFT minted
  2. 5b. You want to mint an NFT + register in the same step

5a. You already have an NFT minted

In your .env file, add the contract address and token ID of where your NFT was minted from:

NFT_CONTRACT_ADDRESS=<NFT_CONTRACT_ADDRESS>
TOKEN_ID=<TOKEN_ID>

Now that we have an NFT contract address set in our .env file, the code below will register your NFT as an 🧩 IP Asset, set License Terms on the IP, and then set both NFT & IP metadata.

import { PIL_TYPE, RegisterIpResponse } from '@story-protocol/core-sdk'
import { Address } from 'viem'

const response: RegisterIpResponse = await client.ipAsset.register({
  nftContract: process.env.NFT_CONTRACT_ADDRESS as Address,
  tokenId: process.env.TOKEN_ID as number,
  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}`)

5b. You want to mint an NFT + register in the same step

First, in a separate script, 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.

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 { StoryClient, StoryConfig } from '@story-protocol/core-sdk'
import { http } from 'viem

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

const config: StoryConfig = {
  account: account,
  transport: http(process.env.RPC_PROVIDER_URL),
  chainId: 'iliad',
}
const client = StoryClient.newClient(config)

const newCollection = await client.nftClient.createNFTCollection({
  name: 'Test NFT',
  symbol: 'TEST',
  txOptions: { waitForTransaction: true },
})

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

Look at the console output, and copy the NFT contract address. Add that value as NFT_CONTRACT_ADDRESS to your .env file:

NFT_CONTRACT_ADDRESS=<NFT_CONTRACT_ADDRESS>

📘

Note

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

Now that we have an NFT contract address set in our .env file, 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 { PIL_TYPE, CreateIpAssetWithPilTermsResponse } from '@story-protocol/core-sdk'
import { Address } from 'viem'

const response: CreateIpAssetWithPilTermsResponse = await client.ipAsset.mintAndRegisterIpAssetWithPilTerms({
  nftContract: process.env.NFT_CONTRACT_ADDRESS as Address,
  pilType: PIL_TYPE.NON_COMMERCIAL_REMIX,
  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}`)

6. Done!

👍

See the Completed Code

To see a completed, working example of how to add metadata to your IP Asset, please see the metadata tutorial.

Note: The completed code follows step 5b. If you want to follow step 5a instead, you can very easily swap out the code.