Skip to main content
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. This NFT is the ownership over the IP. Then you register that NFT on Story, turning it into an IP Asset. The below tutorial will walk you through how to do this.

Prerequisites

There are a few steps you have to complete before you can start the tutorial.
  1. Complete the TypeScript SDK Setup
  2. [OPTIONAL] Go to Pinata and create a new API key. Add the JWT to your .env file:
.env
PINATA_JWT=<YOUR_PINATA_JWT>
  1. [OPTIONAL] Install the pinata-web3 dependency:
Terminal
npm install pinata-web3

1. Set up your IP Metadata

We can set metadata on our NFT & IP, but you don’t have to. To do this, view the IPA Metadata Standard and construct your metadata for both your NFT & IP.
main.ts
// you should already have a client set up (prerequisite)
import { client } from "./utils";

async function main() {
  const ipMetadata = {
    title: "Ippy",
    description: "Official mascot of Story.",
    image:
      "https://ipfs.io/ipfs/QmSamy4zqP91X42k6wS7kLJQVzuYJuW2EN94couPaq82A8",
    imageHash:
      "0x21937ba9d821cb0306c7f1a1a2cc5a257509f228ea6abccc9af1a67dd754af6e",
    mediaUrl:
      "https://ipfs.io/ipfs/QmSamy4zqP91X42k6wS7kLJQVzuYJuW2EN94couPaq82A8",
    mediaHash:
      "0x21937ba9d821cb0306c7f1a1a2cc5a257509f228ea6abccc9af1a67dd754af6e",
    mediaType: "image/png",
    creators: [
      {
        name: "Story Foundation",
        address: "0x67ee74EE04A0E6d14Ca6C27428B27F3EFd5CD084",
        description: "The World's IP Blockchain",
        contributionPercent: 100,
        socialMedia: [
          {
            platform: "Twitter",
            url: "https://twitter.com/storyprotocol",
          },
          {
            platform: "Website",
            url: "https://story.foundation",
          },
        ],
      },
    ],
  };
}

main();

2. Set up your NFT Metadata

The NFT Metadata follows the ERC-721 Metadata Standard.
main.ts
import { IpMetadata } from "@story-protocol/core-sdk";
import { client } from "./utils";

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

  const nftMetadata = {
    name: "Ownership NFT",
    description: "This is an NFT representing owernship of our IP Asset.",
    image: "https://picsum.photos/200",
  };
}

main();

3. Upload your IP and NFT Metadata to IPFS

In a separate uploadToIpfs file, create a function to upload your IP & NFT Metadata objects to IPFS:
uploadToIpfs.ts
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:
main.ts
import { IpMetadata } from "@story-protocol/core-sdk";
import { client } from "./utils";
import { uploadJSONToIPFS } from "./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();

4. Register an NFT as an IP Asset

Remember that in order to register a new IP, we first have to mint an NFT, which will represent the underlying ownership of the IP. This NFT then gets “registered” and becomes an IP Asset. Luckily, we can use the registerIpAsset function to mint an NFT and register it as an IP Asset in the same transaction. This function needs an SPG NFT Contract to mint from.

4a. What SPG NFT contract address should I use?

For simplicity, you can use a public collection we have created for you on Aeneid testnet: 0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc. On Mainnet, or even when testing a real scenario on Aeneid, you should create your own contract as described in the “Using a custom ERC-721 contract” section below.
Using a public collection we provide for you is fine, but when you do this for real, you should make your own NFT Collection for your IPs. You can do this in 2 ways:
  1. Deploy a contract that implements the ISPGNFT interface, or use the SDK’s createNFTCollection function (shown below) to do it for you. This will give you your own SPG NFT Collection that only you can mint from.
createSpgNftCollection.ts
import { zeroAddress } from "viem";
import { client } from "./utils";

async function createSpgNftCollection() {
  const newCollection = await client.nftClient.createNFTCollection({
    name: "Test NFTs",
    symbol: "TEST",
    isPublicMinting: false,
    mintOpen: true,
    mintFeeRecipient: zeroAddress,
    contractURI: "",
  });

  console.log("New collection created:", {
    "SPG NFT Contract Address": newCollection.spgNftContract,
    "Transaction Hash": newCollection.txHash,
  });
}

createSpgNftCollection();
  1. Create a custom ERC-721 NFT collection on your own. See a working code example here. This is helpful if you already have a custom NFT contract that has your own custom logic, or if your IPs themselves are NFTs.
Here is the code to register an IP:
Associated Docs: ipAsset.registerIpAsset
main.ts
import { IpMetadata } from "@story-protocol/core-sdk";
import { client } from "./utils";
import { uploadJSONToIPFS } from "./uploadToIpfs";
import { createHash } from "crypto";
import { Address } from "viem";

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

  const response = await client.ipAsset.registerIpAsset({
    nft: {
      type: "mint",
      spgNftContract: "0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc",
    },
    ipMetadata: {
      ipMetadataURI: `https://ipfs.io/ipfs/${ipIpfsHash}`,
      ipMetadataHash: `0x${ipHash}`,
      nftMetadataURI: `https://ipfs.io/ipfs/${nftIpfsHash}`,
      nftMetadataHash: `0x${nftHash}`,
    },
  });

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

main();

5. Add License Terms to IP

During the registration process, you can attach License Terms to the IP. This will allow others to mint a license and use your IP, restricted by the terms.
main.ts
import {
  IpMetadata,
  PILFlavor,
  WIP_TOKEN_ADDRESS,
} from "@story-protocol/core-sdk";
import { client } from "./utils";
import { uploadJSONToIPFS } from "./uploadToIpfs";
import { createHash } from "crypto";
import { Address, parseEther } from "viem";

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

  const response = await client.ipAsset.registerIpAsset({
    nft: {
      type: "mint",
      spgNftContract: "0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc",
    },
    licenseTermsData: [
      {
        terms: PILFlavor.commercialRemix({
          commercialRevShare: 5,
          defaultMintingFee: parseEther("1"), // 1 $IP
          currency: WIP_TOKEN_ADDRESS,
        }),
      },
    ],
    ipMetadata: {
      ipMetadataURI: `https://ipfs.io/ipfs/${ipIpfsHash}`,
      ipMetadataHash: `0x${ipHash}`,
      nftMetadataURI: `https://ipfs.io/ipfs/${nftIpfsHash}`,
      nftMetadataHash: `0x${nftHash}`,
    },
  });

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

main();

6. View Completed Code

Congratulations, you registered an IP and attached license terms to it!
I