Registering Story Protocol IP Assets with Arweave Metadata using Turbo
Utilize the speed and reliability of ArDrive Turbo to store metadata for Story Protocol IP Assets permanently on Arweave.
Story Protocol enables the registration and management of intellectual property (IP) on-chain. A crucial part of this process involves linking metadata to your IP Assets. While various storage solutions exist, Arweave offers permanent, decentralized storage, making it an ideal choice for valuable IP metadata.
This guide demonstrates how to use the ArDrive Turbo SDK to efficiently upload IP Asset metadata to Arweave and register it with the Story Protocol TypeScript SDK.
Node.js: Version 18 or later. Download from nodejs.org.
npm/pnpm/yarn: A compatible package manager.
Arweave Wallet: A wallet.json file. Generate one using tools like the Wander browser extension. Keep this file secure and do not commit it to version control.
Turbo Credits: Your Arweave wallet must be funded with Turbo credits to pay for uploads. Top up at https://turbo-topup.com.
Story Protocol Account: An Ethereum-compatible private key (WALLET_PRIVATE_KEY) and an RPC Provider URL (RPC_PROVIDER_URL) for the desired Story Protocol network (e.g., Aeneid testnet) stored in a .env file.
TypeScript Environment: You'll need to execute TypeScript code, so make sure you have ts-node installed globally (npm install -g ts-node) or as a dev dependency.
Now, let's create a script to register an IP asset. This involves three steps:
Define metadata for the IP itself and the NFT representing ownership
Upload metadata to Arweave using Turbo
Register the IP on Story Protocol
Create the following script file:
import { storyClient, turboClient } from"./utils/clients";import { createHash } from"crypto";import { Address } from"viem";importtype { UploadResult } from"@ardrive/turbo-sdk";// Helper function to upload JSON to Arweave via TurboasyncfunctionuploadJSONToArweave(jsonData:any, description:string):Promise<UploadResult> {constdataBuffer=Buffer.from(JSON.stringify(jsonData));console.log(`Uploading ${description} (${dataBuffer.byteLength} bytes) to Arweave via Turbo...`);consttags= [ { name:"Content-Type", value:"application/json" }, { name:"App-Name", value:"ArDrive-Story-Tutorial" } // Example tag ];try {// Use Turbo to upload the file bufferconstresult=awaitturboClient.uploadFile(dataBuffer, { tags });console.log(`${description} uploaded successfully: Transaction ID ${result.id}`);return result; } catch (error) {console.error(`Error uploading ${description} to Arweave:`, error);thrownewError(`Arweave upload failed for ${description}.`); }}asyncfunctionregister() {// --- Step 1: Define IP Metadata ---constipMetadata= { title:"My Arweave-Powered IP", description:"An example IP asset with metadata stored permanently on Arweave via Turbo.",// Add other required fields like image, creators, etc.// Example creator: creators: [ { name:"Your Name/Org", address:storyClient.account.address, contributionPercent:100 }, ], };console.log("IP Metadata defined.");constnftMetadata= { name:"Ownership NFT for My Arweave IP", description:"This NFT represents ownership of the IP Asset whose metadata is on Arweave.",// Add other fields like image };console.log("NFT Metadata defined.");// --- Step 2: Upload Metadata to Arweave ---constipUploadResult=awaituploadJSONToArweave(ipMetadata,"IP Metadata");constnftUploadResult=awaituploadJSONToArweave(nftMetadata,"NFT Metadata");// Use arweave.net URLs instead of ar:// protocolconstipMetadataArweaveURI=`https://arweave.net/${ipUploadResult.id}`;constnftMetadataArweaveURI=`https://arweave.net/${nftUploadResult.id}`;console.log(`IP Metadata Arweave URI: ${ipMetadataArweaveURI}`);console.log(`NFT Metadata Arweave URI: ${nftMetadataArweaveURI}`);// Calculate metadata hashes (required by Story Protocol)constipMetadataHash=`0x${createHash("sha256").update(JSON.stringify(ipMetadata)).digest("hex")}`;constnftMetadataHash=`0x${createHash("sha256").update(JSON.stringify(nftMetadata)).digest("hex")}`;console.log(`IP Metadata Hash: ${ipMetadataHash}`);console.log(`NFT Metadata Hash: ${nftMetadataHash}`);// --- Step 3: Register IP on Story Protocol ---console.log("Registering IP Asset on Story Protocol...");// Choose an SPG NFT contract (Story Protocol Governed NFT)// Use a public testnet one or create your own (see Story docs)constspgNftContract:Address="0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc"; // Aeneid testnet exampletry {constresponse=awaitstoryClient.ipAsset.mintAndRegisterIp({ spgNftContract: spgNftContract, ipMetadata: { ipMetadataURI: ipMetadataArweaveURI,// URI pointing to Arweave ipMetadataHash: ipMetadataHash asAddress,// Content hash nftMetadataURI: nftMetadataArweaveURI,// URI pointing to Arweave nftMetadataHash: nftMetadataHash asAddress// Content hash }, txOptions: { waitForTransaction:true },// Wait for confirmation });console.log(`Successfully registered IP Asset!` );console.log(` Transaction Hash: ${response.txHash}`);console.log(` IP ID: ${response.ipId}`);console.log(` Story Explorer Link: https://aeneid.explorer.story.foundation/ipa/${response.ipId}`); // Adjust explorer link for different networksconsole.log(` IP Metadata (Arweave): ${ipMetadataArweaveURI}`);console.log(` NFT Metadata (Arweave): ${nftMetadataArweaveURI}`); } catch (error) {console.error("Error registering IP Asset on Story Protocol:", error); }}// Execute the register functionregister().catch(console.error);
Run the Registration Script
To execute the script and register your IP Asset:
npxts-noderegisterIpWithArweave.ts
This will:
Upload your IP metadata to Arweave permanently
Upload your NFT metadata to Arweave permanently
Register an IP Asset on Story Protocol pointing to these Arweave URLs
Once an IP Asset is registered, you can attach license terms and allow others to mint license tokens. Create a new script for this:
import { storyClient } from"./utils/clients";import { Address } from"viem";// Assume these values are known for the IP Asset you want to licenseconstLICENSOR_IP_ID:Address="0x..."; // Replace with the actual IP ID of the assetconstLICENSE_TERMS_ID:string="..."; // Replace with the specific terms ID attached to the IP AssetconstRECEIVER_ADDRESS:Address="0x..."; // Address to receive the license token(s)asyncfunctionmintLicense() {console.log(`Minting license token(s) for IP ID ${LICENSOR_IP_ID} under terms ${LICENSE_TERMS_ID}...`);try {constresponse=awaitstoryClient.license.mintLicenseTokens({ licenseTermsId:LICENSE_TERMS_ID, licensorIpId:LICENSOR_IP_ID, receiver:RECEIVER_ADDRESS, amount:1,// Number of license tokens to mint// Optional parameters:// maxMintingFee: BigInt(0), // Set if the terms have a fee; 0 disables check if no fee expected// maxRevenueShare: 100, // Default check for revenue share percentage txOptions: { waitForTransaction:true }, });console.log(`Successfully minted license token(s)!` );console.log(` Transaction Hash: ${response.txHash}`);console.log(` License Token ID(s): ${response.licenseTokenIds}`); } catch (error) {console.error("Error minting license token(s):", error); }}// Execute the function (after updating the constants above)// mintLicense().catch(console.error);
Before running this script:
Replace LICENSOR_IP_ID with the actual IP ID obtained from your registration
Replace LICENSE_TERMS_ID with the ID of license terms attached to that IP
Replace RECEIVER_ADDRESS with the address to receive the license token
Finally, let's create a script to register a derivative work based on an existing IP, also using Arweave for metadata storage:
import { storyClient, turboClient } from"./utils/clients";import { createHash } from"crypto";import { Address } from"viem";importtype { UploadResult } from"@ardrive/turbo-sdk";import { DerivativeData } from"@story-protocol/core-sdk";// Helper function to upload JSON to Arweave via Turbo (same as in registerIpWithArweave.ts)asyncfunctionuploadJSONToArweave(jsonData:any, description:string):Promise<UploadResult> {constdataBuffer=Buffer.from(JSON.stringify(jsonData));console.log(`Uploading ${description} (${dataBuffer.byteLength} bytes) to Arweave via Turbo...`);consttags= [ { name:"Content-Type", value:"application/json" }, { name:"App-Name", value:"ArDrive-Story-Tutorial" } ];try {constresult=awaitturboClient.uploadFile(dataBuffer, { tags });console.log(`${description} uploaded successfully: Transaction ID ${result.id}`);return result; } catch (error) {console.error(`Error uploading ${description} to Arweave:`, error);thrownewError(`Arweave upload failed for ${description}.`); }}// --- Information about the Parent IP and License ---constPARENT_IP_ID:Address="0x..."; // Replace with the actual Parent IP IDconstLICENSE_TERMS_ID:string="..."; // Replace with the License Terms ID to derive underasyncfunctionregisterDerivative() {// --- Step 1: Define Derivative Metadata ---constderivativeIpMetadata= { title:"My Derivative Work (Arweave Metadata)", description:"A remix/adaptation based on a parent IP, metadata on Arweave.",// Add other required fields (image, creators matching the derivative creator, etc.) };constderivativeNftMetadata= { name:"Ownership NFT for My Derivative Work", description:"NFT for the derivative IP, metadata on Arweave.",// Add other fields };// --- Step 2: Upload Derivative Metadata to Arweave ---console.log("Uploading derivative metadata to Arweave via Turbo...");constderivIpUploadResult=awaituploadJSONToArweave(derivativeIpMetadata,"Derivative IP Metadata");constderivNftUploadResult=awaituploadJSONToArweave(derivativeNftMetadata,"Derivative NFT Metadata");// Use arweave.net URLs instead of ar:// protocolconstderivIpMetadataArweaveURI=`https://arweave.net/${derivIpUploadResult.id}`;constderivNftMetadataArweaveURI=`https://arweave.net/${derivNftUploadResult.id}`;constderivIpMetadataHash=`0x${createHash("sha256").update(JSON.stringify(derivativeIpMetadata)).digest("hex")}`;constderivNftMetadataHash=`0x${createHash("sha256").update(JSON.stringify(derivativeNftMetadata)).digest("hex")}`;console.log(`Derivative IP Metadata Arweave URI: ${derivIpMetadataArweaveURI}`);console.log(`Derivative NFT Metadata Arweave URI: ${derivNftMetadataArweaveURI}`);// --- Step 3: Register Derivative on Story Protocol ---// Prepare Derivative Data for Story ProtocolconstderivData:DerivativeData= { parentIpIds: [PARENT_IP_ID], licenseTermsIds: [LICENSE_TERMS_ID], };console.log("Registering Derivative IP Asset on Story Protocol...");// Use the same SPG NFT contract or your ownconstspgNftContract:Address="0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc"; // Aeneid testnet exampletry {constresponse=awaitstoryClient.ipAsset.mintAndRegisterIpAndMakeDerivative({ spgNftContract: spgNftContract, derivData: derivData,// Link to parent IP and license terms ipMetadata: { // Metadata for the *new* derivative IP ipMetadataURI: derivIpMetadataArweaveURI,// Arweave URI ipMetadataHash: derivIpMetadataHash asAddress,// Content hash nftMetadataURI: derivNftMetadataArweaveURI,// Arweave URI nftMetadataHash: derivNftMetadataHash asAddress// Content hash }, txOptions: { waitForTransaction:true }, });console.log(`Successfully registered Derivative IP Asset!` );console.log(` Transaction Hash: ${response.txHash}`);console.log(` Derivative IP ID: ${response.ipId}`);console.log(` Derivative Token ID: ${response.tokenId}`);console.log(` Story Explorer Link: https://aeneid.explorer.story.foundation/ipa/${response.ipId}`);console.log(` Derivative Metadata (Arweave): ${derivIpMetadataArweaveURI}`); } catch (error) {console.error("Error registering derivative IP Asset on Story Protocol:", error); }}// Before running this script:// 1. Replace PARENT_IP_ID with a real IP ID you have access to// 2. Replace LICENSE_TERMS_ID with the actual license terms ID// Then uncomment the line below to execute// registerDerivative().catch(console.error);
Before running this script:
Replace PARENT_IP_ID with the actual parent IP ID
Replace LICENSE_TERMS_ID with the license terms ID that permits derivatives
By leveraging the ArDrive Turbo SDK, you can seamlessly integrate permanent Arweave storage into your Story Protocol workflow. Uploading metadata with Turbo ensures fast, reliable, and cost-effective data persistence for your valuable IP Assets, whether they are root IPs or complex derivatives with licensing relationships.
This tutorial demonstrated a complete workflow:
Setting up a project structure with all required dependencies
Creating a utility module for client initialization
Registering original IP Assets with metadata stored on Arweave
Minting license tokens for IP Assets
Creating and registering derivative works
For further details on Story Protocol concepts like licensing, derivatives, or specific SDK functions, refer to the Story Protocol Documentation.