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.

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.

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. 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 in Step 5, we'll have to have 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'
import { account, RPCProviderUrl } from './utils'

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

const config: StoryConfig = {
  account: account,
  transport: http('https://testnet.storyrpc.io'),
  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>

1. Set up your Story Config

import { StoryClient, StoryConfig } from '@story-protocol/core-sdk'
import { http } from 'viem'
import { account } from './utils/utils'
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('https://testnet.storyrpc.io'),  
  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:

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

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

export async function uploadJSONToIPFS(ipMetadata: IpMetadata): Promise<string> {
    const pinata = new pinataSDK({ pinataJWTKey: process.env.PINATA_JWT })
    const { IpfsHash } = await pinata.pinJSONToIPFS(ipMetadata)
    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

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 } from '@story-protocol/core-sdk'

const registeredIpAssetResponse = await client.ipAsset.mintAndRegisterIpAssetWithPilTerms({
    nftContract: process.env.NFT_CONTRACT_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 ${registeredIpAssetResponse.txHash}, IPA ID: ${registeredIpAssetResponse.ipId}`)
console.log(`View on the explorer: https://explorer.story.foundation/ipa/${registeredIpAssetResponse.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.