# Implementing ATCP/IP Source: https://docs.story.foundation/ai-agents/implementing-atcpip Learn how to practically implement Agent to Agent transactions from the proposed architecture in the ATCP/IP whitepaper. Still new! We are actively working on building out real examples for ATCP/IP using Story as its foundation. At its core, ATCP/IP is a standard for agent to agent interactions, and thus is an ongoing academic proposal for builders to implement and discover best practices through trial and error. Below are details on how to actually implement the ***2. An ATCP/IP Transaction*** section of the whitepaper below. Read our Agent TCP/IP whitepaper, which defines an agent-to-agent transaction system to enable a future of AGI. ## Performing Each Step Below we will show how to implement each step of the ATCP/IP interaction flow as demonstrated by the image below. ### Registering your Agent's Outputs In order to register an agent's outputs (or really any IP) on Story, follow the [How to Register IP on Story](/developers/tutorials/how-to-register-ip-on-story) tutorial. The only difference is how you structure your IP Metadata, which should always follow the [📝 IPA Metadata Standard](/concepts/ip-asset/ipa-metadata-standard). You can also check out more specific tutorials that demonstrate how to register images generated by DALL·E or Stability: * [Protect DALL·E AI-Generated Images](/developers/tutorials/protect-dalle-ai-generated-images) * [Register & Monetize Stability Images](/developers/tutorials/register-stability-images) Here is an example of what the IP Metadata should look like for your generated IP (using a song as an example): ```json theme={null} { "title": "Midnight Marriage", "description": "This is a house-style song generated on suno.", "createdAt": "1740005219", "creators": [ { "name": "Jacob Tucker", "address": "0xA2f9Cf1E40D7b03aB81e34BC50f0A8c67B4e9112", "contributionPercent": 100 } ], "image": "https://cdn2.suno.ai/image_large_8bcba6bc-3f60-4921-b148-f32a59086a4c.jpeg", "imageHash": "0xc404730cdcdf7e5e54e8f16bc6687f97c6578a296f4a21b452d8a6ecabd61bcc", "mediaUrl": "https://cdn1.suno.ai/dcd3076f-3aa5-400b-ba5d-87d30f27c311.mp3", "mediaHash": "0xb52a44f53b2485ba772bd4857a443e1fb942cf5dda73c870e2d2238ecd607aee", "mediaType": "audio/mpeg" } ``` ### Creating Agreement Terms As described in the [Whitepaper](https://story.foundation/atcpip), agents will negotiate on what agreement terms are appropriate for the requested task: 2 **Terms formulation**: The provider agent will consider the request and choose an appropriate set of\ license terms for the information being requested. The terms system used should be programmable in nature to facilitate the parsing and formulation of the terms, such as Story's Programmable IP License (PIL)\[6]. 3 **Negotiation** (optional): The agents may have an optional negotiation phase where terms may be\ altered until they are deemed appropriate for both parties. * **Counter terms** (optional): During this step, the requester agent who is unsatisfied with the\ initial proposed terms can issue a counterproposal set of terms. Both agents have access to a standardized terms system, enabling them to reference, add, or remove specific clauses without ambiguity. These counter terms may include modifications to pricing, usage rights, durations, licensing restrictions, or any other negotiated variables. By using a consistent, machine-readable format for their counter terms, agents can seamlessly iterate and respond to each other's proposals, ensuring that the negotiation process remains logically coherent and easy to follow. * **Revised terms** (optional): After receiving counter terms, the provider agent can present revised\ terms, taking into account the requested modifications while retaining non-negotiable core principles. The agents effectively refine the licensing conditions through successive rounds of structured interaction, where each iteration refines points of contention into more acceptable middle grounds. Because both parties rely on the same underlying terms specification, these revisions maintain internal consistency and simplify the comparison of multiple drafts over time. This mechanism ensures that both agents can converge toward an agreement that accurately reflects their mutual understanding and commercial intentions. * *This process could have multiple iterations until an agreement is reached* Once agents agree on the terms, they can be created and attached to the registered asset: Learn how to attach terms to your IP using the SDK. Learn how to attach terms to your IP using the Smart Contracts. ### Mint a License As stated in the [Whitepaper](https://story.foundation/atcpip), after agents have negotiated on a set of terms, the requester agent can mint a license from the provider agent with specific agreement terms attached: 4 **Acceptance**: The requester agent will formally accept the terms by minting an immutable token (the\ agreement token) that encapsulates the terms and rules by which the information being provided is to be used. Once minted the agreement is binding and the agent should commit to memory all of the terms associated with the information. * **Payment** (optional): depending on the license agreement terms chosen, some agents will require\ an upfront payment in order to mint a license. Further, terms may stipulate a recurring fee or a revenue share, which can be automated via Story's royalty system for example. Once agreement terms are attached to an IP Asset, a [License Token](/concepts/licensing-module/license-token) can be minted: Learn how to mint a License Token using the SDK. Learn how to mint a License Token using the Smart Contracts. Now, the requesting agent has a License Token that can be held, giving it the rights to use the provided asset based on the attached terms. ### Claim Revenue Once the providing agent has been paid for their work (when the requesting agent minted a license that costed \$), they can claim their due revenue: Learn how to claim revenue using the SDK. Learn how to claim revenue using the Smart Contracts. ## Example Integration with MCP We have implemented a Model Context Protocol (MCP) server that provides tools for interacting with Story's protocol using [the MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), and an AI Agent that uses those tools. Run an MCP server locally that has tools for interacting with Story's protocol to test Agent TCP/IP. A LangGraph-based AI agent for creating, minting, and registering IP assets with Story using the Story MCP server. 1. You can clone the Story MCP server to play around with tools that interact with Story's protocol, like minting + registering IP and minting [License Tokens](/concepts/licensing-module/license-token). 2. Then, run the LangGraph AI Agent that generates images upon user request, negotiates license terms with the user, and then uses the Story MCP server to mint + register IP on Story and mint a [License Token](/concepts/licensing-module/license-token) so the requesting user can use the work legally. Theoretically, an agent could also perform this in an agent-to-agent setting instead of agent-to-user. ### What is MCP? > "MCP is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools." Check out the [Model Context Protocol (MCP) website](https://modelcontextprotocol.io/introduction) to learn more. # For AI Agents Source: https://docs.story.foundation/ai-agents/overview Learn about AI Agents on Story. Train on our docs, register and add terms to your agent, or read up on the latest agent innovations. This page is all about AI Agents. We have prepared a way for you to use our documentation as training data which can be seen below, or continue to learn about developing AI Agents on Story. Below you will find two sections: 1. **Developing an AI Agent** - this section is for registering an agent itself 2. **Implementing ATCP/IP** - this section is for implementing the ***2. An ATCP/IP Transaction*** section of the [Whitepaper](https://story.foundation/whitepaper.pdf). ## Developing an Agent Below are details on how to: * Register an AI Agent as IP * Add License Terms to your AI Agent ### Registering an Agent In order to register an AI Agent (or any IP) on Story, follow the [How to Register IP on Story](/developers/tutorials/how-to-register-ip-on-story) tutorial. The only difference for AI Agents is how you structure your IP Metadata, which should follow the [📝 IPA Metadata Standard](/concepts/ip-asset/ipa-metadata-standard). Here is an example of what the IP Metadata should look like for your AI Agent: ```json theme={null} { "title": "Story AI Agent", "description": "This is an example AI Agent registered on Story.", "createdAt": "1740005219", "creators": [ { "name": "Jacob Tucker", "address": "0xA2f9Cf1E40D7b03aB81e34BC50f0A8c67B4e9112", "contributionPercent": 100 } ], "image": "https://ipfs.io/ipfs/bafybeigi3k77t5h5aefwpzvx3uiomuavdvqwn5rb5uhd7i7xcq466wvute", "imageHash": "0x64ccc40de203f218d16bb90878ecca4338e566ab329bf7be906493ce77b1551a", "mediaUrl": "https://ipfs.io/ipfs/bafybeigi3k77t5h5aefwpzvx3uiomuavdvqwn5rb5uhd7i7xcq466wvute", "mediaHash": "0x64ccc40de203f218d16bb90878ecca4338e566ab329bf7be906493ce77b1551a", "mediaType": "image/webp", "aiMetadata": { "characterFileUrl": "https://ipfs.io/ipfs/bafkreic6eu4hlnwx46soib62rgkhhmlieko67dggu6bzk7bvtfusqsknfu", "characterFileHash": "0x5e253875b6d7e7a4e407da899473b168229def8cc6a783957c35996928494d2d" }, "ipType": "AI Agent", "tags": ["AI Agent", "Twitter bot", "Smart Agent"] } ``` ### Adding Terms to your AI Agent Upon registering your AI Agent, you can add license terms to it. However you can add more license terms to your AI Agent afterwards as well. Follow the below tutorials to learn how to do this: Learn how to attach terms to your AI Agent using the SDK. Learn how to attach terms to your AI Agent using the Smart Contracts. ## Implementing ATCP/IP See [Implementing the ATCP/IP Whitepaper](/ai-agents/implementing-atcpip). # Using Cursor with Story Source: https://docs.story.foundation/ai-agents/using-cursor-with-story Add the entire Story documentation to Cursor. [Cursor](https://www.cursor.com/) is an AI code editor that makes it easy to write code while building Story apps. Let's walk through how to setup Cursor for the best possible results with Story. ## Add Story Docs Adding Story docs lets you interact with our docs directly and get the most accurate answers to your questions. 1. Go to Cursor Settings > Features > Docs and click "+ Add new doc" 2. Paste the URL `https://docs.story.foundation` This is our entire documentation combined into a single `.md` file, **which is automatically updated every single time our docs have changes.** 3. Change the name to Story, and leave everything else the same ## Using the Docs You can then reference the Story docs in your prompt with the `@Story` symbol. # Access Controller Source: https://docs.story.foundation/concepts/access-controller Manages all permission-related states and permission checks on Story. View the smart contract for the Access Controller. Access Controller manages all permission-related states and permission checks on Story. In particular, it maintains the *Permission Table* and *Permission Engine* to process and store permissions. IPAccount permissions are set by the IPAccount owner. Access Controller Diagram ## Permission Table ### Permission Record | IPAccount | Signer (caller) | To (only module) | Function Sig | Permission | | ---------- | --------------- | ---------------- | ------------ | ---------- | | 0x123..111 | 0x789..222 | 0x790..333 | 0xAaAaAaAa | Allow | | 0x123..111 | 0x789..222 | 0x790..333 | 0xBBBBBBBB | Deny | | 0x123..111 | 0x789..222 | 0x790..333 | 0xCCCCCC | Abstain | Each record defines a permission in the form of the **Signer** (caller) calling the **Func** of the **To** (module) on behalf of the **IPAccount**. The permission field can be set as "Allow," "Deny," or "Abstain." Abstain indicates that the permission decision is determined by the upper-level permission. ### Wildcard Wildcard is also supported when defining permissions; it defines a permission that applies to multiple modules and/or functions. With wildcards, users can easily define a whitelist or blacklist of permissions. | IPAccount | Signer (caller) | To (module) | Func | Permission | | :--------- | :-------------- | :---------- | :--- | :--------- | | 0x123..111 | 0x789..222 | \* | \* | Allow | | 0x123..111 | 0x789..222 | 0x790..333 | \* | Deny | The above example shows that the signer (0x789...) is unable to invoke any functions of the module (0x790...) on behalf of the IPAccount (0x123...). In other words, the IPAccount has blacklisted the signer from calling any functions on the module 0x790...333 * Supported wildcards: | Parameter | Wildcard | | -------------------------- | ---------- | | Func | bytes4(0) | | Addresses (IPAccount / To) | address(0) | ### Permission Prioritization Specific permissions override general permissions. | IPAccount | Signer (caller) | To (module) | Func | Permission | | :--------- | :-------------- | :---------- | :--------- | :--------- | | 0x123..111 | 0x789..222 | \* | \* | Allow | | 0x123..111 | 0x789..222 | 0x790..333 | \* | Deny | | 0x123..111 | 0x789..222 | 0x790..333 | 0xCCCCDDDD | Allow | The above shows that the signer (0x789...) is not allowed to call any functions of the module (0x790...) on behalf of IPAccount (0x123...), except for the function 0xCCCCDDDD Furthermore, the signer (0x789...) is permitted to call all other modules on behalf of IPAccount (0x123...). ## Call Flows with Access Control There exist three types of call flows expected by the Access Controller. 1. An IPAccount calls a module directly. 2. A module calls another module directly. 3. A module calls a registry directly. ### IPAccount calling a Module directly * IPAccount performs a permission check with the Access Controller. * The module only needs to check if the `msg.sender` is a valid IPAccount. When calling a module from an IPAccount, the IPAccount performs an access control check with AccessController to determine if the current caller has permission to make the call. In the module, it only needs to check whether the transaction `msg.sender` is a valid IPAccount. `AccessControlled` provide a modifier `onlyIpAccount()` helps to perform the access control check. ```solidity Solidity theme={null} contract MockModule is IModule, AccessControlled { function action(string memory param) external view onlyIpAccount() returns (string memory) { // do something } } ``` IPAccount calling Module ## Module calling another Module * The callee module needs to perform the authorization check itself. When a module is called directly from another module, it is responsible for performing the access control check using AccessController. This check determines whether the current caller has permission to make the call to the module. `AccessControlled` provide a modifier `verifyPermission(address ipAccount)` helps to perform the access control check. ```solidity Solidity theme={null} contract MockModule is IModule, AccessControlled { function callFromAnotherModule(address ipAccount) external verifyPermission(ipAccount) returns (string memory) { if (!IAccessController(accessController).checkPermission(ipAccount, msg.sender, address(this), this.callFromAnotherModule.selector)) { revert Unauthorized(); } // do something } } ``` Module calling Module ## Module calling Registry * The registry performs the authorization check by calling AccessController. * The registry authorizes modules through set global permission When a registry is called by a module, it can perform the access control check using AccessController. This check determines whether the callee module has permission to call the registry. ```solidity Solidity theme={null} // called by StoryProtocl Admin IAccessController(accessController).setGlobalPermission(address(0), address(module), address(registry), bytes4(0))) { ``` ```solidity Solidity theme={null} contract MockRegistry { function registerAction() external returns (string memory) { if (!IAccessController(accessController).checkPermission(address(0), msg.sender, address(this), this.registerAction.selector)) { revert Unauthorized(); } // do something } } ``` Module calling Registry The IPAccount's permissions will be revoked upon transfer of ownership. The permissions associated with the IPAccount are exclusively linked to its current owner. When the ownership of the IPAccount is transferred to a new individual, the existing permissions granted to the previous owner are automatically revoked. This ensures that only the current, legitimate owner has access to these permissions. If, in the future, the IPAccount ownership is transferred back to the original owner, the permissions that were initially revoked will be reinstated, restoring the original owner's access and control. # ❌ Dispute Module Source: https://docs.story.foundation/concepts/dispute-module/overview A way for users to raise and resolve disputes through arbitration The Dispute Module creates a way for users to raise and resolve disputes through arbitration. View the smart contract for the Dispute Module. ## Dispute Terminology The main components of the arbitration system are: * **Arbitration Policies:** the arbitration policy refers to the set rules/process/entities that combined will decide on a dispute. Currently the only supported arbitration policy is the [UMA Arbitration Policy](/concepts/dispute-module/uma-arbitration-policy). * **Arbitration Penalty:** what happens to an IP Asset after it has been "tagged". An IPA is not deemed "tagged" unless the dispute is decided to be correct. Once tagged, an IPA will not be able to: * mint licenses * link to any parents * claim royalties * and all of its existing licenses become unusable ### Dispute Tags **Tags** refer to the "labels" that can be applied to IP Assets in the protocol when raising a dispute. **Tags must be whitelisted by protocol governance to be used in a dispute.** The initial set of tags (and their `bytes32` for interacting with the Dispute Module on-chain) are: * `IMPROPER_REGISTRATION`: `0x494d50524f5045525f524547495354524154494f4e0000000000000000000000` * `IMPROPER_USAGE`: `0x494d50524f5045525f5553414745000000000000000000000000000000000000` * `IMPROPER_PAYMENT`: `0x494d50524f5045525f5041594d454e5400000000000000000000000000000000` * `CONTENT_STANDARDS_VIOLATION`: `0x434f4e54454e545f5354414e44415244535f56494f4c4154494f4e0000000000` * `IN_DISPUTE`: `0x494e5f4449535055544500000000000000000000000000000000000000000000` | Dispute Tag | Explanation | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IMPROPER_REGISTRATION` | Refers to registration of IP that already exists. | | `IMPROPER_USAGE`

Examples (non-exhaustive):
Territory,
Channels of Distribution,
Expiration,
Irrevocable,
Attribution,
Derivatives,
Limitations on Creation of Derivatives,
Commercial Use,
Sublicensable,
Non-Transferable,
Restriction on Cross-Platform Use | Refers to improper use of an IP Asset across multiple items (examples on the left). These items can be found in more detail in the [💊 Programmable IP License (PIL)](/concepts/programmable-ip-license/overview) legal document. | | `IMPROPER_PAYMENT` | Refers to missing payments associated with an IP. | | `CONTENT_STANDARDS_VIOLATION`

Examples: No-Hate, Suitable-for-All-Ages, No-Drugs-or-Weapons, No-Pornography | Refers to "No-Hate", "Suitable-for-All-Ages", "No-Drugs-or-Weapons" and "No-Pornography". These items can be found in more detail in the [💊 Programmable IP License (PIL)](/concepts/programmable-ip-license/overview) legal document. | | `IN_DISPUTE` | Different from the other 4, this is a temporary tag that goes away at the end of a dispute and is replaced by "0x" in case of no infringement or is replaced by one of the other tags. | ## Dispute Process Flow Dispute Process Flow ### Raise Dispute The `raiseDispute` function is permissionless and allows any address to raise a dispute against any IP Asset registered on the protocol. The dispute initiator has to: 1. Select which "tag" it is raising a dispute on which will be applied to the IP Asset if the arbitration decision is positive. This means an IP Asset is officially "tagged" only when the proposed tag is confirmed as correct ("positive decision" in the diagram above). 2. Submit the dispute evidence for evaluation 3. Other conditions custom to each arbitration policy - such as payment rules, etc. ### Set Dispute Judgement The `setDisputeJudgement` can only be called by whitelisted addresses and allows the caller to set the dispute judgment. Can only be called once as dispute decisions are immutable. If 3rd parties want to offer the possibility for recourse they can do so on their end and relay the final judgment. ### Tag Derivative If Parent Infringed If the `setDisputeJudgement` has tagged an IP as infringing then any address can call `tagIfRelatedIpInfringed` to apply the same tag as the parent to the derivatives all the way down the derivative chain or if the IP is a group then the group member tag can be applied to any group IP which it is a member of. Looking Ahead In the future, the idea is that any related IP Asset of an infringing IP Asset would automatically be tagged without needing someone to call `tagIfRelatedIpInfringed`. This is currently a limitation that we are aware of. The derivatives are then tagged directly without any need for judgment given that it is considered that if a parent IP license has been infringed then all derivatives that come from that license are also implicitly in an infringement situation. **Example**: IPA 7 is first tagged ("PLAGIARISM") as infringing via `setDisputeJudgement` after having gone through a dispute process. Only after that can IPAs 3, 1, and 0 can be tagged via `tagIfRelatedIpInfringed` by any address without needing to go through a new dispute process. Dispute Example ### Resolve Dispute Resolving a dispute removes the tag from the IP Asset. Since there are two ways in which a tag can be applied, there are two ways for it to be resolved: 1. Tag was applied via the`setDisputeJudgement` function In a case where a dispute judgment was positive, then a tag was applied. After the tag has been applied to an IP Asset, the **dispute initiator** can, if he/she believes the matter to be resolved and the tag to no longer apply, choose to remove it by calling `resolveDispute`. For example, if one party owed money to the dispute initiator and paid the full amount after the dispute judgment then the tag could be cleared and the IP Asset would have a clean slate again. If the dispute initiator chooses to not resolve, then the tag that was defined in `setDisputeJudgement` remains in force. 2. Tag was applied via the`tagIfRelatedIpInfringed` function If an IP has been previously tagged as infringing via `tagIfRelatedIpInfringed`, such tag can be removed via `resolveDispute` in a permissionless way as long as the parent is no longer considered an infringing IP Asset. This mechanism of permissionless resolving disputes exists to make it easier to propagate down the derivative chain and remove infringement tags from derivative IPs when the parent has resolved its original dispute and is no longer considered as being in an infringing situation, and therefore neither are its derivatives. If no address chooses to resolve, then the tag that was applied from the parent to the derivative remains in force. ### Cancel Dispute In a case where a dispute was raised but the matter has been resolved before the dispute judgment, the dispute initiator can cancel the dispute. However, depending on the conditions of each arbitration policy, there may be non-refundable fees that are not recouped on cancellation. Currently, the [UMA Arbitration Policy](/concepts/dispute-module/uma-arbitration-policy) does not support cancelling disputes. # UMA Arbitration Policy Source: https://docs.story.foundation/concepts/dispute-module/uma-arbitration-policy A dispute resolution mechanism using UMA's optimistic oracle For detailed information on how UMA's dispute resolution works, [visit their website](https://uma.xyz/). This arbitration policy is a dispute resolution mechanism that uses UMA’s optimistic oracle to verify disputes. Below we share a high-level overview of how the UMA dispute process works. ## Smart Contract Flow Diagram UMA Arbitration Flow The first step to initiate a dispute against an IP Asset is to call the `raiseDispute` function on [DisputeModule.sol](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/modules/dispute/DisputeModule.sol). This function will in turn call `assertTruth` on UMA's `OptimisticOracleV3.sol`. To initiate a dispute the dispute initiator will need to post a bond of at least the minimum bond defined by UMA for the selected currency. Note that this bond will be lost if the dispute is deemed not verifiably correct by the oracle. UMA will be adjusting the minimum \$IP bond size as the IP price fluctuates. The correct way to obtain the current minimum bond size is via `getMinimumBond()` on `OptimisticOracleV3.sol` (OOV3), found on our [aeneid testnet](https://aeneid.storyscan.io/address/0xABac6a158431edED06EE6cba37eDE8779F599eE4?tab=read_write_contract#0x4360af3d) and [mainnet](https://www.storyscan.io/address/0x8EF424F90C6BC1b98153A09c0Cac5072545793e8?tab=read_write_contract#0x4360af3d). After this step, the dispute will be “open” to be countered/appealed by other users. If there is no counter/appeal, UMA rules define that the IP will be considered to be infringing. ```sol DisputeModule.sol theme={null} /// @notice Raises a dispute on a given ipId /// @param targetIpId The ipId that is the target of the dispute /// @param disputeEvidenceHash The hash pointing to the dispute evidence - this could be an IPFS CID converted to a bytes32 hash. This is the document with the proof that UMA reviewers will potentially read /// @param targetTag The target tag of the dispute /// @param data The data to initialize the policy - here you can do abi.encode of liveness, token address and bond amount /// @return disputeId The id of the newly raised dispute function raiseDispute( address targetIpId, bytes32 disputeEvidenceHash, bytes32 targetTag, bytes calldata data ) external returns (uint256 disputeId); ``` After the `raiseDispute` call there is a period of time called "liveness" in which a counter dispute/appeal can be submitted. The liveness period is split in two parts: (i) the first part of the liveness period in which only the IP owner can counter dispute/appeal and (ii) a second part in which any address can counter dispute/appeal - which can be done by calling `disputeAssertion` on `ArbitrationPolicyUMA.sol`. To counter a dispute the caller will need to post a bond of the same amount and currency that was used by the dispute initiator when raising a dispute. Note that this bond will be lost if the original dispute is deemed to be verifiably correct by the oracle. After this step, the dispute is escalated and will be reviewed by external party UMA. ```sol ArbitrationPolicyUMA.sol theme={null} /// @notice Allows the IP that was targeted with a dispute to dispute the assertion while providing counter evidence /// @param assertionId The identifier of the assertion that was disputed /// @param counterEvidenceHash The hash of the counter evidence function disputeAssertion(bytes32 assertionId, bytes32 counterEvidenceHash) external; /// @notice Returns the assertion id for a given dispute id /// @param disputeId The dispute id function disputeIdToAssertionId(uint256 disputeId) external view returns (bytes32); ``` UMA reviewers judge the dispute. On this step the user just has to wait until the UMA reviewers make the dispute judgement. This step could take 48-96 hours. This step is expected to be automatic as UMA runs a bot that calls `settleAssertion` which in turn distributes the bonds back to the address that wins the dispute. 1. If nobody submitted a counter dispute then when the liveness period is over, any address can call `settleAssertion` on UMA's `OptimisticOracleV3.sol`. 2. If somebody has submitted a counter dispute/appeal before the liveness period is over, then the dispute is escalated to UMA reviewers who will judge and make a decision on whether the IP is infringing or not. After the decision has been made, then any address can call `settleAssertion` on UMA's `OptimisticOracleV3.sol`. ## Dispute Evidence Submission Guidelines When raising a dispute or making a counter dispute, both parties can submit dispute evidence. Dispute evidence refers to a text document that oracle participants will use & read from to make a judgement on the dispute. ### Burden of Proof In all disputes with UMA arbitration policy, the burden of proof lies with the party creating the dispute. This means that the disputer must provide clear, compelling, and verifiable evidence to prove the dispute beyond reasonable doubt. Disputes that do not meet this high bar can be counter-disputed with the disputing party losing their bond. ### Document Characteristics As the process is still experimental, we can expect iteration and fine-tuning on the contents/formats of how the evidence should be submitted. Every document should have the following characteristics: * It should be a text document. Can have images or video if necessary. * It should be uploaded on IPFS. * It should not take the reviewer more than 1 hour to review the dispute evidence document - the reviewer's time is limited and the evidence could be deemed invalid if it would take too much time to review. Best efforts will be applied to solve a dispute but please keep it concise to have your dispute evidence be valid. Depending on what the type of the Dispute Tag is, you also need to include in the evidence the "Dispute Evidence Contents of the table below: | Dispute Tag | Dispute Evidence Contents | Dispute review process (Human reviewer instructions) | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `IMPROPER_REGISTRATION` | A. Showcase or pointer to the pre-existing IP that is being infringed upon by the disputed IP

B. Proof of public display of the pre-existing IP at an earlier date than the infringing IP (onchain or offchain) and/or instructions on where/how to check it | 1. Check if the pre-existing is the same or very similar to the disputed IP using input A
- Mickey Mouse with 1 pixel difference is an infringement
- Mickey Mouse with a new hat is an infringement unless it's a derivative of the original Mickey Mouse with an appropriate license
2. Check the registration date of the pre-existing IP using input B
3. Confirm that the disputed IP has a later registration date
4. Confirm that the disputed IP is not a derivative of the pre-existing IP

| | `IMPROPER_USAGE`

Examples (non-exhaustive):
Territory,
Channels of Distribution,
Expiration,
Irrevocable,
Attribution,
Derivatives,
Limitations on Creation of Derivatives,
Commercial Use,
Sublicensable,
Non-Transferable,
Restriction on Cross-Platform Use | A. PIL term that has been violated

B. Description of the violation

C. Proof of the violation | 1. Read the associated PIL term description on the PIL license official document using input A
2. Read the violation description using input B
3. Decide on the veracity of the proof presented by checking on associated platforms when possible using input C

| | `IMPROPER_PAYMENT` | A. Description of each payment the disputed IP received that should have been shared with its royalty vault and/or its ancestors but it were not

B. Proof of those payments that were not properly shared as royalties | 1. Check veracity of the proof of payments by checking on the associated platforms when possible using input A and B
2. If proof of payments are deemed to be real, confirm that the payment has indeed not been made onchain by checking on the blockchain explorer. Payments should be made calling payRoyaltyOnBehalf() function on RoyaltyModule.sol smart contract. In addition, royalty payments must be made within 15 days of when the capital was originally received by the owner/IP who is paying those royalties.

| | `CONTENT_STANDARDS_VIOLATION`

No-Hate,
Suitable-for-All-Ages,
No-Drugs-or-Weapons,
No-Pornography | A. The content standard point that has been violated

B. Description of the violation

C. Proof of violation | 1. Read the associated content standards description on the official content standards section in the PIL using input A
2. Read the violation description using input B
3. Decide on the veracity of the proof presented by checking on associated platforms when possible using input C

| # Concepts FAQ Source: https://docs.story.foundation/concepts/faq Common technical questions related to our protocol documentation. ## *"What is the difference between License Tokens, Royalty Tokens, and Revenue Tokens?"* | | License Tokens | Royalty Tokens | Revenue Tokens | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Module** | [📜 Licensing Module](/concepts/licensing-module) | [💸 Royalty Module](/concepts/royalty-module) | [💸 Royalty Module](/concepts/royalty-module) | | **Explanation** | An ERC-721 NFT that gets minted from an IP Asset with specific license terms. It is essentially the license you hold that gives you access to use the associated IP Asset based on the terms in the License Token.

A License Token is burned when it is used to register an IP Asset as a derivative of another. | Each IP Asset has 100,000,000 Royalty Tokens associated, where each token represents the right of whoever owns them to claim 0.000001% of the gains ("*Revenue Tokens*") deposited into the IPA's Royalty Vault. | These are the tokens that are actually used for payment (ex. \$WIP).

"*Royalty Tokens*" are used to claim these Revenue Tokens when an IP Asset earns them. | | **Associated Docs** | [License Token](/concepts/licensing-module/license-token) | [IP Royalty Vault](/concepts/royalty-module/ip-royalty-vault) | [IP Royalty Vault](/concepts/royalty-module/ip-royalty-vault) | # 👥 Grouping Module Source: https://docs.story.foundation/concepts/grouping-module Create and manage group IP Assets, supporting a royalty pool for the group. The Grouping Module enables the creation and management of group IP Assets, supporting a royalty pool for the group. View the smart contract for the Grouping Module. `GroupingModule.sol` is the main entry point for the grouping workflows. It is **stateless** and responsible for: * Registering a new group * Adding an IPA to a group * Removing an IPA from a group * Checking whether a group contains a specific IPA * Get the total number of IPAs of a group ## Creating a Group IPA Similar to the IP Asset registration process, in which you must have a minted NFT to register and then an IP Account is created, the same applies to Group IP Assets. You must have a minted ERC-721 NFT (that represents the ownership of the group) to register as a group, and then when you register, an IP Account for the group is deployed. Anyone can create a new group. ### Group IP Asset Registry Similar to how when an IP Asset is created an IP Account is deployed & registered through the [IP Asset Registry](/concepts/registry/ip-asset-registry), the Group's IP Account is deployed and registered through the [Group IP Asset Registry](/concepts/registry/group-ip-asset-registry). This is responsible for managing the registration and tracking of Group IP Assets, including the group members and reward pools. ### The Group's IP Account The Group IP Account should function equivalently to a normal IP Account, allowing attachment of license terms, creation of derivatives, execution with modules, and other interactions. It also has the same common interface of IP Account. Hence, the Group IP Account can be applied to anywhere where IP Account can be applied. Besides the common interfaces of IP Account, the Group IP Account has functions to manage the adding/removing of individual IPAs in the group. ## Group Restrictions Here are the restrictions associated with a Group IPA: * A derivative IP of a group IP can only have the group IP as its sole parent * A group IP cannot attach License Terms that use the [Liquid Absolute Percentage (LAP)](/concepts/royalty-module/liquid-absolute-percentage) royalty policy * An empty group cannot have derivative IPs or mint License Tokens * A group IP cannot be registered as a derivative * A group IP can only attach one license term common to all members * Once a Group gains its first member, the `mintingFee`, `licensingHook`, and `licensingHookData` are frozen. The Group's `commercialRevShare` can only increase * A group has a maximum size of 1000 members ### Adding & Removing from a Group * Only the owner of a group can add/remove IP Assets. You **do not** have to own an IP Asset to add it to your group. * An IPA must include one license terms that matches the license terms of the group (same `licenseTemplate` and `licenseTerms`. An IPA may include other license terms in addition to the one that matches the group. * When adding an IP to a Group, the Group and IP must have the same `mintingFee` and `licenseHook` in the `LicenseConfig`. Additionally, the Group's commercial revenue share must be greater than or equal to the IP's commercial revenue share. ### Groups Becoming Locked When a group is locked, IPAs cannot be removed from it, but new IPAs can still be added. A group IPA is locked when: 1. it has a derivative IP registered OR 2. when someone mints a license token from the group ## Example Let's say you have an AI bot that uses training data to continuously learn and produce better content. The training data is a Group IPA that is the root, and the AI bot is a derivative IPA of the training data. And any time the AI bot gets paid, the revenue flows back to the training data as revenue. Now you want to add more training data to the group. Since the group is now locked (you linked a derivative to it), you should register a new Group IPA as a root, and then a new AI bot as a derivative. # 🪝 Hooks Source: https://docs.story.foundation/concepts/hooks Add custom logic before minting license tokens or registering derivatives. Hooks allow developers to create custom implementations, restrictions, and functionality upon minting [License Tokens](/concepts/licensing-module/license-token) or registering derivatives. There are two types of hooks: 1. **Licensing Hooks**: allow you to [add custom logic before minting license tokens](/concepts/licensing-module/license-config#logic-that-is-possible-with-license-config) (and registering derivatives). For example, requesting a dynamic price, limiting the amount of license tokens that can be minted, whitelists, etc. Licensing Hooks can be added / modified on a licensing config at any point. 2. **Commercializer Checker Hooks**: similar to Licensing Hooks, however they are directly a part of the license terms and do not change. You also cannot return a custom minting fee. ## Licensing Hooks These are contracts that implement the `ILicensingHook` interface, which extends from `IModule`. Most importantly, a Licensing Hook implements a `beforeMintLicenseTokens` function, which is a function that is called before a License Token is minted to implement [custom logic](/concepts/licensing-module/license-config#logic-that-is-possible-with-license-config) and determine the final `totalMintingFee` of that License Token. View the `ILicensingHook` smart contract [here](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/interfaces/modules/licensing/ILicensingHook.sol#L26). ```solidity ILicensingHook.sol theme={null} /// @notice This function is called when the LicensingModule mints license tokens. /// @dev The hook can be used to implement various checks and determine the minting price. /// The hook should revert if the minting is not allowed. /// @param caller The address of the caller who calling the mintLicenseTokens() function. /// @param licensorIpId The ID of licensor IP from which issue the license tokens. /// @param licenseTemplate The address of the license template. /// @param licenseTermsId The ID of the license terms within the license template, /// which is used to mint license tokens. /// @param amount The amount of license tokens to mint. /// @param receiver The address of the receiver who receive the license tokens. /// @param hookData The data to be used by the licensing hook. /// @return totalMintingFee The total minting fee to be paid when minting amount of license tokens. function beforeMintLicenseTokens( address caller, address licensorIpId, address licenseTemplate, uint256 licenseTermsId, uint256 amount, address receiver, bytes calldata hookData ) external returns (uint256 totalMintingFee); ``` Note that it returns the `totalMintingFee`. You may be wondering, "I can set the minting fee in the License Terms, in the `LicenseConfig`, and return a dynamic price from `beforeMintLicenseTokens`. What will the final minting fee actually be?" Here is the priority: | Minting Fee | Importance | | ------------------------------------------------------------- | ---------------- | | The `totalMintingFee` returned from `beforeMintLicenseTokens` | Highest Priority | | The `mintingFee` set in the `LicenseConfig` | ⬇️ | | The `mintingFee` set in the License Terms | Lowest Priority | Beware of potentially malicious implementations of external license hooks. Please first verify the code of the hook you choose because it may be not reviewed or audited by the Story team. ### Available Hooks Below are available hooks deployed on our protocol that you can use. View the deployed addresses for these hooks [here](/developers/deployed-smart-contracts#license-hooks). | Hook | Description | Contract Code | | :------------------------- | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | | LockLicenseHook | Stop the minting of license tokens or registering new derivatives. | [View here ↗️](https://github.com/storyprotocol/protocol-periphery-v1/blob/main/contracts/hooks/LockLicenseHook.sol) | | TotalLicenseTokenLimitHook | Set a limit on the amount of license tokens that can be minted, updatable at any time. | [View here ↗️](https://github.com/storyprotocol/protocol-periphery-v1/blob/main/contracts/hooks/TotalLicenseTokenLimitHook.sol) | ### Implementing the Hooks A working TypeScript SDK code example that shows how to implement a licensing hook. More specifically, how to limit the # of licenses that can be minted. A working Solidity code example that shows how to implement a licensing hook. More specifically, how to limit the # of licenses that can be minted. Licensing Hooks are ultimately a smart contract that implements the `ILicensingHook` interface. You can view the interface [here](https://github.com/storyprotocol/protocol-periphery-v1/blob/main/contracts/interfaces/ILicensingHook.sol). We have a few Licensing Hooks deployed already (view the chart above). In order to actually use a Licensing Hook, you must set it in the Licensing Config, which is basically a set of configurations that you set on License Terms when attaching terms to an IP Asset. First you have to create a Licensing Config: ```typescript {6-8} theme={null} import { LicensingConfig } from '@story-protocol/core-sdk'; const licensingConfig: LicensingConfig = { isSet: true, mintingFee: 0n, // address of TotalLicenseTokenLimitHook // from https://docs.story.foundation/developers/deployed-smart-contracts licensingHook: '0xaBAD364Bfa41230272b08f171E0Ca939bD600478', hookData: zeroAddress, commercialRevShare: 0, disabled: false, expectMinimumGroupRewardShare: 0, expectGroupRewardPool: zeroAddress, } ``` Next, we'll set the Licensing Config on the License Terms. In the following example, we'll show this happening upon registering the IP Asset: This code snippet requires a bit of setup, and it meant for developers who already understand how to setup the TypeScript SDK. If you want to learn more, check out the [working code example](https://github.com/storyprotocol/typescript-tutorial/blob/main/scripts/licenses/oneTimeUseLicense.ts). This uses the `registerIpAsset` method found [here](/sdk-reference/ipasset#registeripasset). ```typescript {6-7} theme={null} const response = await client.ipAsset.registerIpAsset({ nft: { type: 'mint', spgNftContract: '0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc', // public spg contract for ease-of-use }, licenseTermsData: [ { terms: { defaultMintingFee: 0, commercialUse: true, ... }, // dummy license terms // set the licensing config here licensingConfig: licensingConfig }, ], ipMetadata: { ipMetadataURI: 'test-uri', ipMetadataHash: toHex('test-metadata-hash', { size: 32 }), nftMetadataHash: toHex('test-nft-metadata-hash', { size: 32 }), nftMetadataURI: 'test-nft-uri', } }) console.log(`Token ID: ${response.tokenId}, IPA ID: ${response.ipId}, License Terms ID: ${response.licenseTermsIds}`); ``` Now that we have set the Licensing Config on our terms, we can call the `setTotalLicenseTokenLimit` function on the hook and set the max # of licenses that can be minted to 1. ```typescript theme={null} const hookResponse = await client.license.setMaxLicenseTokens({ ipId: response.ipId, licenseTermsId: response.licenseTermsIds![0], maxLicenseTokens: 1000, }); console.log(`Max license tokens set at transaction hash ${hookResponse.txHash}`); ``` ### Creating a New Licensing Hook Please follow the below process for creating a new licensing hook and getting it whitelisted into Story's protocol. 1. **Develop your Hook**: You may fork [this template repository](https://github.com/storyprotocol/hook-dev-template) to bootstrap your development. It includes an example hook with tests against our Aeneid protocol, but using it is optional. See [license-caller-whitelist-hook](https://github.com/jacob-tucker/license-caller-whitelist-hook) as an example that was used to develop the `LicenseCallerWhitelistHook.sol` hook. 2. **Fork Registered Modules Repository**: Fork the [registered-modules repository](https://github.com/storyprotocol/registered-modules) to your own GitHub account. 3. **Update the Module List**: In your `registered-modules` repository, add your hook's details to the `hook-modules.json` file. Make sure to **deploy & verify on block explorer** your hook on both aeneid and mainnet. Ensure that you adhere to the following JSON structure: ```json theme={null} { "name": "YourModuleName", "aeneid": { "address": "YourModuleAddress", "blockExplorerLink": "YourModuleBlockExplorerLink" }, "mainnet": { "address": "YourModuleAddress", "blockExplorerLink": "YourModuleBlockExplorerLink" } } ``` Replace `YourModuleName`, `YourModuleAddress`, and `YourModuleBlockExplorerLink` with your hook's name, its address, and the link to its block explorer page, respectively. Example: ```json theme={null} { "name": "LicenseCallerWhitelistHook", "aeneid": { "address": "0x37be56d9fb06d885cda3cb010096c94c28b4d658", "blockExplorerLink": "https://aeneid.storyscan.io/address/0x37be56d9fb06d885cda3cb010096c94c28b4d658?tab=contract" }, "mainnet": { "address": "0x6d9d51a444c8318e8840e75dab7ed81b5a714610", "blockExplorerLink": "https://www.storyscan.io/address/0x6d9d51a444c8318e8840e75dab7ed81b5a714610?tab=contract" } } ``` 4. **Create a Pull Request (PR)**: Once you have added your hook, create a pull request against this repository. In your PR's description, add the following information (replace the values with your own): ```md theme={null} ## Register my module - Module type: `hook` - Module name: `LicenseCallerWhitelistHook` - Aeneid Module address: `0x37be56d9fb06d885cda3cb010096c94c28b4d658` - Mainnet Module address: `0x6d9d51a444c8318e8840e75dab7ed81b5a714610` - My module is immutable: yes - My module is using an upgradeable proxy: no - My module has been verified on the block explorer (required): yes - Summary of my module: Hook for allowing a licensor to gate which addresses can mint a license. The licensor can add/remove an address at any time. - GitHub repository with source code and tests: https://github.com/jacob-tucker/license-caller-whitelist-hook ``` 5. **Await Verification**: After your PR is submitted, it will be reviewed. Once a security audit has been performed and completed, and the module whitelisted into the protocol, the PR will be merged. At this point your module will be officially registered and recognized as safe for use within the Story community. We look forward to seeing your contributions and expanding the Story module ecosystem! ## Commercializer Checker Hooks Documentation coming soon. If you have questions in the meantime, ask in the [Builder's Discord](https://discord.gg/storybuilders). # ⚙️ IP Account Source: https://docs.story.foundation/concepts/ip-asset/ip-account A modified ERC-6551 implementation bound to an IP Asset Skip the Read Get a quick 2-minute overview of IP Accounts [here](https://twitter.com/jacobmtucker/status/1787603252198134234). When an [🧩 IP Asset](/concepts/ip-asset/overview) is registered, it is given an associated **IP Account**. An IP Account is a modified ERC-6551 (Token Bound Account) implementation. It is a separate contract bound to the IP Asset for controlling permissions around interactions with Story's modules or storing the IP's associated data. Upon registration, an IP Asset is assigned a unique ID. This ID is the address of the IP Account that is bound to the IP Asset. IP Account Diagram An IP Account mainly does two things: 1. Stores comprehensive IP-related data, including metadata and ownership details of associated assets such as the License Tokens or Royalty Tokens that are created from the IP. 2. Facilitates the utilization of this data by various modules. These modules interact with and contribute to the IP Account, creating and storing data. For example, licensing, revenue/royalty sharing, remixing, disputing an IP, and other modules are made possible due to the IP Account's programmability. If the underlying NFT is transferred, the new owner is also automatically the owner of the associated IP Asset & IP Account. ## `execute` and `executeWithSig` A key feature of IP Account is the generic `execute()` function, which allows calling arbitrary modules within Story via encoded bytes data (thus extensible for future modules). Additionally, there is a `executeWithSig()` function that enables users to sign transactions and have others execute on their behalf for seamless UX. # 📝 IPA Metadata Standard Source: https://docs.story.foundation/concepts/ip-asset/ipa-metadata-standard An overview of the IP-specific metadata standard We are still figuring out the best way to define an IPA Metadata Standard. For the sake of transparency, the following document is our thoughts so far but is subject to change as we release future versions. Check out the official Ippy IP, which has both NFT & IP metadata. Learn how to actually add the IP metadata discussed here to your IP Asset with an explanation or completed code example. This is the JSON metadata that is associated with an IP Asset, and gets stored inside of an IP Account. You must call `setMetadata(...)` inside of the IP Account in order to set the metadata, and then call `metadata()` to read it. ## Attributes & Structure Below are the important attributes you should provide in your IP metadata. Under the **Required For** column is what the specific field is required for: * 🔍 Story Explorer - this field will help display your IP on the Story Explorer * 🕵️ [Commercial Infringement Check](/concepts/story-attestation-service) - this field is required if your IP is **commercial** (that is, has `commercialUse = true` license terms attached). We will use these fields to run an infringement check on your IP. * See [current limitations](/concepts/story-attestation-service#current-limitations). * 🤖 AI Agents - used for displaying metadata associated with AI Agents | Property Name | Type | Description | Required For | | ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | `title` | `string` | Title of the IP | 🔍 Story Explorer | | `description` | `string` | Description of the IP | 🔍 Story Explorer | | `createdAt` | `string` | Date/Time that the IP was created (either ISO8601 or unix format). This field can be used to specify historical dates that aren't on-chain. For example, Harry Potter was published on June 26. | 🔍 Story Explorer | | `image` | `string` | An image for your IP. **For audio assets, the recommended thumbnail aspect ratio is 1:1. For video assets, it is 16:9.** | 🔍 Story Explorer | | `imageHash` | `string` | Hash of your `image` using SHA-256 hashing algorithm. See [here](#hashing-content) for how that is done. | 🔍 Story Explorer | | `creators` | `IpCreator[]` | An array of information about the creators. [See the type defined below](#type-definitions) | 🔍 Story Explorer | | `mediaUrl` | `string` | Used for infringement checking, points to the actual media (ex. image or audio). **For audio assets, the recommended thumbnail aspect ratio is 1:1. For video assets, it is 16:9.** | 🕵️ [Commercial Infringement Check](/concepts/story-attestation-service) | | `mediaHash` | `string` | Hashed string of the media using SHA-256 hashing algorithm. See [here](#hashing-content) for how that is done. | 🕵️ [Commercial Infringement Check](/concepts/story-attestation-service) | | `mediaType` | `string` | Type of media (audio, video, image), based on [mimeType](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types). See the allowed media types [here](#media-types). | 🕵️ [Commercial Infringement Check](/concepts/story-attestation-service) | | `aiMetadata` | `AIMetadata` | Used for registering & displaying AI Agent Metadata. [See the type defined below](#type-definitions) | 🤖 AI Agents | | N/A | N/A | You can include other values as well. | N/A | ### Type Definitions Here are the type definitions for the complex types used in the metadata: ```typescript IpCreator theme={null} type IpCreator = { name: string; address: Address; contributionPercent: number; // add up to 100 description?: string; image?: string; socialMedia?: IpCreatorSocial[]; role?: string; }; type IpCreatorSocial = { platform: string; url: string; }; ``` ```typescript AIMetadata theme={null} type AIMetadata = { // this can be any character file you want // example: https://github.com/elizaOS/characterfile/blob/main/examples/example.character.json characterFileUrl: string; characterFileHash: string; }; ``` ### Media Types The following media types are allowed for the `mediaType` field: | Media Type | Description | | ----------------- | --------------------- | | `image/jpeg` | JPEG image | | `image/png` | PNG image | | `image/apng` | Animated PNG image | | `image/avif` | AV1 Image File Format | | `image/gif` | GIF image | | `image/svg+xml` | SVG image | | `image/webp` | WebP image | | `audio/wav` | WAV audio | | `audio/mpeg` | MP3 audio | | `audio/flac` | FLAC audio | | `audio/aac` | AAC audio | | `audio/ogg` | OGG audio | | `audio/mp4` | MP4 audio | | `audio/x-aiff` | AIFF audio | | `audio/x-ms-wma` | WMA audio | | `audio/opus` | Opus audio | | `video/mp4` | MP4 video | | `video/webm` | WebM video | | `video/quicktime` | QuickTime video | ### Hashing Content To hash content for the `imageHash` or `mediaHash` fields, you can use the SHA-256 hashing algorithm. Here's an example of how to do this in JavaScript: ```typescript TypeScript theme={null} import { toHex, Hex } from "viem"; // get hash from a file async function getFileHash(file: File): Promise { const arrayBuffer = await file.arrayBuffer(); const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer); return toHex(new Uint8Array(hashBuffer), { size: 32 }); } // get hash from a url async function getHashFromUrl(url: string): Promise { const response = await axios.get(url, { responseType: "arraybuffer" }); const buffer = Buffer.from(response.data); return "0x" + createHash("sha256").update(buffer).digest("hex"); } ``` ```shell Shell theme={null} shasum -a 256 myfile.jpg ``` ### Example Use Cases This is the official Ippy mascot that is registered on mainnet. You can view it on our protocol explorer [here](https://explorer.story.foundation/ipa/0xB1D831271A68Db5c18c8F0B69327446f7C8D0A42). ```json theme={null} { "title": "Ippy", "description": "Official mascot of Story.", "createdAt": "1728401700", "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": "Telegram", "url": "https://t.me/StoryAnnouncements" }, { "platform": "Website", "url": "https://story.foundation" }, { "platform": "Discord", "url": "https://discord.gg/storyprotocol" }, { "platform": "YouTube", "url": "https://youtube.com/@storyFDN" } ] } ], "tags": ["Ippy", "Story", "Story Mascot", "Mascot", "Official"], // experimental field "ipType": "Character" // experimental field } ``` This is an example song generated on [Suno](https://suno.com/) and registered on our testnet. View the below example [on our protocol explorer](https://aeneid.explorer.story.foundation/ipa/0x7d126DB8bdD3bF88d757FC2e99BFE3d77a55509b). ```json theme={null} { "title": "Midnight Marriage", "description": "This is a house-style song generated on suno.", "createdAt": "1740005219", "creators": [ { "name": "Jacob Tucker", "address": "0xA2f9Cf1E40D7b03aB81e34BC50f0A8c67B4e9112", "contributionPercent": 100 } ], "image": "https://cdn2.suno.ai/image_large_8bcba6bc-3f60-4921-b148-f32a59086a4c.jpeg", "imageHash": "0xc404730cdcdf7e5e54e8f16bc6687f97c6578a296f4a21b452d8a6ecabd61bcc", "mediaUrl": "https://cdn1.suno.ai/dcd3076f-3aa5-400b-ba5d-87d30f27c311.mp3", "mediaHash": "0xb52a44f53b2485ba772bd4857a443e1fb942cf5dda73c870e2d2238ecd607aee", "mediaType": "audio/mpeg" } ``` The main difference here is you should supply `aiMetadata` with a character file. You can provide any character file you want, or use [this ElizaOS example](https://github.com/elizaOS/characterfile/blob/main/examples/example.character.json) as a template. View the below example [on our protocol explorer](https://aeneid.explorer.story.foundation/ipa/0x49614De8b2b02C790708243F268Af50979D568d4). ```json theme={null} { "title": "Story AI Agent", "description": "This is an example AI Agent registered on Story.", "createdAt": "1740005219", "creators": [ { "name": "Jacob Tucker", "address": "0xA2f9Cf1E40D7b03aB81e34BC50f0A8c67B4e9112", "contributionPercent": 100 } ], "image": "https://ipfs.io/ipfs/bafybeigi3k77t5h5aefwpzvx3uiomuavdvqwn5rb5uhd7i7xcq466wvute", "imageHash": "0x64ccc40de203f218d16bb90878ecca4338e566ab329bf7be906493ce77b1551a", "mediaUrl": "https://ipfs.io/ipfs/bafybeigi3k77t5h5aefwpzvx3uiomuavdvqwn5rb5uhd7i7xcq466wvute", "mediaHash": "0x64ccc40de203f218d16bb90878ecca4338e566ab329bf7be906493ce77b1551a", "mediaType": "image/webp", "aiMetadata": { "characterFileUrl": "https://ipfs.io/ipfs/bafkreic6eu4hlnwx46soib62rgkhhmlieko67dggu6bzk7bvtfusqsknfu", "characterFileHash": "0x5e253875b6d7e7a4e407da899473b168229def8cc6a783957c35996928494d2d" } } ``` ## Optional Properties The following properties are optional but can provide additional context about your IP Asset: We are still figuring out the best way to define an IPA Metadata Standard. The fields below are bound to change or be removed at some point. | Property Name | Type | Description | | :--------------- | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ipType` | `string` | Type of the IP Asset, can be defined arbitrarily by the creator. I.e. "character", "chapter", "location", "items", "music", etc | | `relationships` | `IpRelationship[]` | The detailed relationship info with the IPA's direct parent asset, such as `APPEARS_IN`, `FINETUNED_FROM`, etc. See more examples [here](#relationship-types). | | `watermarkImage` | `string` | A separate image with your watermark already applied. This way apps choosing to use it can render this version of the image (with watermark applied). | | `media` | `IpMedia[]` | An array of supporting media. Media type defined below | | `app` | `StoryApp` | This is assigned to verified application from Story directly (on a request basis so far). We will map each App ID to a name | | `tags` | `string[]` | Any tags that can help surface this IPA | | `robotTerms` | `IPRobotTerms` | Allows you to set Do Not Train for a specific agent | | N/A | N/A | You can include other values as well. | ### Type Definitions ```typescript IpRelationship theme={null} type IpRelationship = { parentIpId: Address; type: string; // see "Relationship Types" docs below }; ``` ```typescript IpMedia theme={null} type IpMedia = { name: string; url: string; mimeType: string; }; ``` ```typescript StoryApp theme={null} type StoryApp = { id: string; name: string; website: string; action?: string; }; ``` ```typescript IPRobotTerms theme={null} type IPRobotTerms = { userAgent: string; allow: string; }; ``` ### Relationship Types The different relationship types that can be used for the `relationships` attribute. #### Story Relationships 1. **APPEARS\_IN** - A character APPEARS\_IN a chapter. 2. **BELONGS\_TO** - A chapter BELONGS\_TO a book. 3. **PART\_OF** - A book is PART\_OF a series. 4. **CONTINUES\_FROM** - A chapter CONTINUES\_FROM the previous one. 5. **LEADS\_TO** - An event LEADS\_TO a consequence. 6. **FORESHADOWS** - An event FORESHADOWS future developments. 7. **CONFLICTS\_WITH** - A character CONFLICTS\_WITH another character. 8. **RESULTS\_IN** - A decision RESULTS\_IN a significant change. 9. **DEPENDS\_ON** - A subplot DEPENDS\_ON the main plot. 10. **SETS\_UP** - A prologue SETS\_UP the story. 11. **FOLLOWS\_FROM** - A chapter FOLLOWS\_FROM the previous one. 12. **REVEALS\_THAT** - A twist REVEALS\_THAT something unexpected occurred. 13. **DEVELOPS\_OVER** - A character DEVELOPS\_OVER the course of the story. 14. **INTRODUCES** - A chapter INTRODUCES a new character or element. 15. **RESOLVES\_IN** - A conflict RESOLVES\_IN a particular outcome. 16. **CONNECTS\_TO** - A theme CONNECTS\_TO the main narrative. 17. **RELATES\_TO** - A subplot RELATES\_TO the central theme. 18. **TRANSITIONS\_FROM** - A scene TRANSITIONS\_FROM one setting to another. 19. **INTERACTED\_WITH** - A character INTERACTED\_WITH another character. 20. **LEADS\_INTO** - An event LEADS\_INTO the climax.?\ **PARALLEL - story** happening in parallel or around the same timeframe #### AI Relationships 1. **TRAINED\_ON** - A model is TRAINED\_ON a dataset. 2. **FINETUNED\_FROM** - A model is FINETUNED\_FROM a base model. 3. **GENERATED\_FROM** - An image is GENERATED\_FROM a fine-tuned model. 4. **REQUIRES\_DATA** - A model REQUIRES\_DATA for training. 5. **BASED\_ON** - A remix is BASED\_ON a specific workflow. 6. **INFLUENCES** - Sample data INFLUENCES model output. 7. **CREATES** - A pipeline CREATES a fine-tuned model. 8. **UTILIZES** - A workflow UTILIZES a base model. 9. **DERIVED\_FROM** - A fine-tuned model is DERIVED\_FROM a base model. 10. **PRODUCES** - A model PRODUCES generated images. 11. **MODIFIES** - A remix MODIFIES the base workflow. 12. **REFERENCES** - An AI-generated image REFERENCES original data. 13. **OPTIMIZED\_BY** - A model is OPTIMIZED\_BY specific algorithms. 14. **INHERITS** - A fine-tuned model INHERITS features from the base model. 15. **APPLIES\_TO** - A fine-tuning process APPLIES\_TO a model. 16. **COMBINES** - A remix COMBINES elements from multiple datasets. 17. **GENERATES\_VARIANTS** - A model GENERATES\_VARIANTS of an image. 18. **EXPANDS\_ON** - A fine-tuning process EXPANDS\_ON base capabilities. 19. **CONFIGURES** - A workflow CONFIGURES a model’s parameters. 20. **ADAPTS\_TO** - A fine-tuned model ADAPTS\_TO new data. # IP Modifications & Restrictions Source: https://docs.story.foundation/concepts/ip-asset/ipa-modifications Learn about the modifications and restrictions for IP Assets # IP Asset Modifications IP Assets can be modified/customized a few ways. For example, by [setting the License Config](/concepts/licensing-module/license-config) which allows you to change a few things as you'll see below, changing its metadata, and more. These things can **always be changed unless there is a certain condition**. | Action | Conditions | Via The... | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Modify License Minting Fee | You can **increase** the minting fee. You **cannot** decrease it. | [License Config](/concepts/licensing-module/license-config) | | Modify Licensing Hook | The hook must be whitelisted in the [Module Registry](/concepts/registry/module-registry). | [License Config](/concepts/licensing-module/license-config) | | Modify `commercialRevShare` | You can **increase** the rev share percentage. You **cannot** decrease it.

However, you **can** set it to 0 to disable the overwrite. | [License Config](/concepts/licensing-module/license-config) | | Disable/Enable the License | License can be disabled or re-enabled at any time.

*Note that disabling a license disallows future licenses from being minted, but does not affect existing ones.* | [License Config](/concepts/licensing-module/license-config) | | Modify Metadata | Cannot modify if the metadata is **frozen**. This is done by calling `freezeMetadata` in the [CoreMetadataModule.sol](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/modules/metadata/CoreMetadataModule.sol). | [CoreMetadataModule.sol](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/modules/metadata/CoreMetadataModule.sol) | ## License Hook Modifications The IP can be further customized or modified using the [License Hook](/concepts/hooks#licensing-hooks). This is a function that gets set within the License Config that gets called before a [License Token](/concepts/licensing-module/license-token) (or more simply, a "license") is minted. There are various features you can implement with the License Hook, and are **always modifiable**: | Feature | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------- | | Dynamic License Fee | You can dynamically set the price of a license. For example, it can be updated dynamically via bonding curve logic. | | Total # of Licenses | You can abort the function based on a maximum number of license tokens that can be minted. | | Specific Receivers | You can restrict minting of license to a specific receiver. | | More... | Additional licensing hook features can be implemented as required. | # Group IPA Restrictions See [👥 Group IPA Restrictions](/concepts/grouping-module#group-restrictions) for more information. # 🧩 IP Asset Source: https://docs.story.foundation/concepts/ip-asset/overview The foundational programmable IP metadata on Story Skip the Read Get a quick 1-minute overview of IP Assets [here](https://twitter.com/jacobmtucker/status/1785765362744889410). IP Assets are the foundational programmable IP metadata on Story. Each IP Asset is an on-chain ERC-721 NFT (representing an IP). If your IP is off-chain, you would simply mint an ERC-721 NFT to represent that IP first, and then register it as an IP Asset. When an IP Asset is created, an associated [⚙️ IP Account](/concepts/ip-asset/ip-account) is deployed, which is a modified ERC-6551 (Token Bound Account) implementation. It is a separate contract bound to the IP Asset for controlling permissions around interactions with Story's modules or storing the IP's associated data. ## Registering an IP Asset An IP Asset is created by registering an ERC-721 NFT into Story's global [IP Asset Registry](/concepts/registry/ip-asset-registry). If you'd like to jump into code examples/tutorials, please see [How to Register IP on Story](/developers/tutorials/how-to-register-ip-on-story). ## NFT vs. IP Metadata On Story, your IP is an NFT that gets registered on the protocol as an IP Asset. However, both NFTs and IP Assets have their own metadata you can set, so what's the difference? | | Standard | What is it? | | :------ | :------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- | | **NFT** | [Opensea ERC721 Standard](https://docs.opensea.io/docs/metadata-standards) | Things like `name`, `description`, `image`, `attributes`, `animation_url`, etc | | **IP** | [📝 IPA Metadata Standard](/concepts/ip-asset/ipa-metadata-standard) | More specific to Story, this includes necessary information about the underlying content for infringement checks, authors of the work, etc | All other metadata, such as the ownership, legal, and economic details of an IP Asset are handled by our protocol directly. For example, the protocol stores data associated with parent-child relationships through the [📜 Licensing Module](/concepts/licensing-module/overview), the monetary flow between IP Assets through the [💸 Royalty Module](/concepts/royalty-module/overview), and the legal constraints/permissions of an IP Asset with the [💊 Programmable IP License (PIL)](/concepts/programmable-ip-license/overview). ### Adding NFT & IP Metadata to IP Asset Jump to the code and see a completed code example of adding NFT & IP metadata to an IP Asset Learn how to add metadata to your IP Asset with a step-by-step explanation. In practice, whether you are using the SDK or our smart contract directly, our protocol asks you to provide 4 different parameters: * View the `WorkflowStructs.sol` contract [here](https://github.com/storyprotocol/protocol-periphery-v1/blob/main/contracts/lib/WorkflowStructs.sol). ```solidity WorkflowStructs.sol theme={null} /// @notice Struct for metadata for NFT minting and IP registration. /// @dev Leave the nftMetadataURI empty if not minting an NFT. /// @param ipMetadataURI The URI of the metadata for the IP. /// @param ipMetadataHash The hash of the metadata for the IP. /// @param nftMetadataURI The URI of the metadata for the NFT. /// @param nftMetadataHash The hash of the metadata for the IP NFT. struct IPMetadata { string ipMetadataURI; bytes32 ipMetadataHash; string nftMetadataURI; bytes32 nftMetadataHash; } ``` * `ipMetadataURI` - a URI pointing to a JSON object that follows the [📝 IPA Metadata Standard](/concepts/ip-asset/ipa-metadata-standard) * `ipMetadataHash` - hash of the `ipMetadataURI` JSON object * `nftMetadataURI` - a URI pointing to a JSON object that follows the [Opensea ERC721 Standard](https://docs.opensea.io/docs/metadata-standards) * `nftMetadataHash` - hash of the `nftMetadataURI` JSON object # License Config Source: https://docs.story.foundation/concepts/licensing-module/license-config An optional config that can be attached to a specific license for dynamic minting fees and custom logic. ## License Config View the LicensingConfig struct in the smart contract. Optionally, you can attach a `LicensingConfig` to an IP Asset (for a specific `licenseTermsId` attached to that asset) which contains fields like a `mintingFee` and a `licensingHook`, as shown below. ```solidity theme={null} /// @notice This struct is used by IP owners to define the configuration /// when others are minting license tokens of their IP through the LicensingModule. /// When the `mintLicenseTokens` function of LicensingModule is called, the LicensingModule will read /// this configuration to determine the minting fee and execute the licensing hook if set. /// IP owners can set these configurations for each License or set the configuration for the IP /// so that the configuration applies to all licenses of the IP. /// If both the license and IP have the configuration, then the license configuration takes precedence. /// @param isSet Whether the configuration is set or not. /// @param mintingFee The minting fee to be paid when minting license tokens. /// @param licensingHook The hook contract address for the licensing module, or address(0) if none /// @param hookData The data to be used by the licensing hook. /// @param commercialRevShare The commercial revenue share percentage. /// @param disabled Whether the license is disabled or not. /// @param expectMinimumGroupRewardShare The minimum percentage of the group's reward share /// (from 0 to 100%, represented as 100 * 10 ** 6) that can be allocated to the IP when it is added to the group. /// If the remaining reward share in the group is less than the minimumGroupRewardShare, /// the IP cannot be added to the group. /// @param expectGroupRewardPool The address of the expected group reward pool. /// The IP can only be added to a group with this specified reward pool address, /// or address(0) if the IP does not want to be added to any group. struct LicensingConfig { bool isSet; uint256 mintingFee; address licensingHook; bytes hookData; uint32 commercialRevShare; bool disabled; uint32 expectMinimumGroupRewardShare; address expectGroupRewardPool; } ``` What do some of these mean? 1. `isSet` - if this is false, the whole licensing config is completely ignored. So for example, if the licensing config has `mintingFee == 10` and `disabled == true`, but the `isSet == false`, the `mintingFee` and `disabled` will be completely ignored. 2. `disabled` - if this is true, then no licenses can be minted and no more derivatives can be attached at all for the terms the config is attached to. Fields like the `mintingFee` and `commercialRevShare` overwrite their duplicate in the license terms themselves. **A benefit of this is that derivative IP Assets, which normally cannot change their license terms, are able to overwrite certain fields.** The `licensingHook` is an address to a smart contract that implements the `ILicensingHook` interface, which contains a `beforeMintLicenseTokens` function which will be run before a user mints a License Token. This means you can insert logic to be run upon minting a license. The hook itself is described in a different section. You can see it contains information about the license, who is minting the License Token, and who is receiving it. Learn all about Licensing Hooks [here](/concepts/hooks#licensing-hooks). ### Setting the License Config You can set the License Config by calling the `setLicenseConfig` function in the [LicensingModule.sol contract](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/modules/licensing/LicensingModule.sol). ### Logic that is Possible with License Config 1. **Max Number of Licenses**: The `licensingHook` (described in the next section) is where you can define logic for the max number of licenses that can be minted. For example, reverting the transaction if the max number of licenses has already been minted. 2. **Disallowing Derivatives**: If you register a derivative of an IP Asset, that derivative cannot change its License Terms as described [here](/concepts/licensing-module/license-terms#inherited-license-terms). You can be wondering: "What if I, as a derivative, want to disallow derivatives of myself, but my License Terms allow derivatives and I cannot change this?" To solve this, you can simply set `disabled` to true. 3. **Minting Fee**: Similar to #2 above... what about the minting fee? Although you cannot change License Terms on a derivative IP Asset (and thus the minting fee inside of it), you can change the minting fee for that derivative by modifying the `mintingFee` in the License Config, or returning a `totalMintingFee` from the `licensingHook` (described in the next section). 4. **Commercial Revenue Share**: Similar to #2 and #3 above, you can modify the `commercialRevShare` in the License Config. 5. **Dynamic Pricing for Minting a License Token**: Set dynamic pricing for minting a License Token from an IP Asset based on how many total have been minted, how many licenses the user is minting, or even who the user is. All of this data is available in the `licensingHook` (described in the next section). ... and more. ### Restrictions See [IP Modifications & Restrictions](/concepts/ip-asset/ipa-modifications) for the various restrictions on setting the License Config. # License Template Source: https://docs.story.foundation/concepts/licensing-module/license-template A legal framework, written in code ("programmable"), that defines various licensing terms for an IP A License Template is a legal framework, written in code ("programmable"), that defines various licensing terms for an IP. Such as: * "Is commercial use allowed?" - true/false (bool) * "Is the license transferrable?" - true/false (bool) * "If commercial, what % of royalty do I receive?" - number These terms and values differ per License Template. The first (and currently only) example of a License Template was developed by the Story team directly, and is called the Programmable IP License (PIL :pill:). Learn about the first implementation of a License Template View the smart contract for the PIL. ## License Template Requirements License Templates are responsible for: * Providing a link to the actual, off-chain, legal contract template, with all the parameters, their possible values, and the correspondent legalese, in `licenseTextUrl`. * For a licensing framework to be compatible with Story, the legal text **must** be clear and parametrized, with each licensing parameter establishing the possible outcomes of each value. * The parameter values in each License Template (called "License Template terms") drive the legal text for each license agreement. * Defining a `struct` with the particular definitions of the parameters in accordance, which must be encoded into the License Terms struct (described below). * Providing registration methods for the License Terms, and getters. * **Verifying** that both the **minter** and the address **linking a derivative are allowed by the License Template terms to perform those actions**. * These conditions could be enforced by the License Template itself or through hooks. They can range from limitations on the derivative creations, token-gating LNFT holders, creative control from licensors, KYC, etc. It's up to the implementation of each License Template. * **Verifying that the License Terms are compatible if a derivative has or will have multiple parents** ## Create Your Own Template You can create your own License Template (like the PIL), but it must be approved by the Story team to be fully embedded into the protocol. # License Terms Source: https://docs.story.foundation/concepts/licensing-module/license-terms A particular combination of values from a License Template that define how others can interact with your IP When registering your IP on Story, you can attach License Terms to the IP. These are real, legally binding terms enforced on-chain by the [📜 Licensing Module](/concepts/licensing-module/overview), disputable by the [❌ Dispute Module](/concepts/dispute-module/overview), and in the worst case, able to be enforced off-chain in court through traditional means. In them are also terms for commercial usage, which describes how the [💸 Royalty Module](/concepts/royalty-module/overview) will be enforced (ex. "50% of revenue must be shared with the parent IP"). View some popular combinations of PIL License Terms, also known as "flavors". More specifically, License Terms are a particular combination of values from a [License Template](/concepts/licensing-module/license-template). Indeed, there can and will exist **multiple** License Terms (variations) for each License Template. You can imagine that a License Template generates many License Term variations. License Terms Diagram Once registered, **License Terms are immutable — they can't be tampered with or altered**, even by the License Template that generated it. Additionally, License Terms have a unique numeric ID within the License Template they stem from. This makes License Terms reusable, meaning if someone creates License Terms with a specific set of values, it only needs to be created once and can be used by anyone else. For example, a particular set of term values of the [Programmable IP License (PIL💊)](/concepts/programmable-ip-license/overview), such as non-commercial usage + derivatives allowed + free minting, defines a unique License Terms with an associated ID. ## License Terms Attached to IP Asset The owner of a root IP Asset can attach License Terms to signal to other users that they can mint License Tokens of those terms to create a derivative of this IP Asset. **Once License Terms are attached to an IP Asset, it is now considered "public" and anyone can mint a License Token using those terms.** License Terms Attached to IP Asset ## Inherited License Terms On the other hand, derivative IP Assets inherit their License Terms from the parent IP Asset. This means that when an IP Asset registers itself as a derivative, it burns the License Token and inherits the associated License Terms. **The owner of this derivative cannot set new License Terms.** You may be wondering: "if I cannot set new License Terms on my derivative, does that also mean I can't change the minting fee, or disallowing more derivatives, on my derivative?" Thankfully, there is a way to get around this! Although you cannot change License Terms on a derivative IP, you can utilize the [License Config to implement special behaviors](/concepts/licensing-module/license-config). ## Expiration License Terms support an `expiration` time. Once License Terms expire, any derivatives that abide by that license will no longer be able to generate revenue or create further derivatives. If an IP Asset is a derivative of multiple parents, it will expire when the soonest expiration time between the two parents is reached. # License Token Source: https://docs.story.foundation/concepts/licensing-module/license-token An ERC-721 NFT that allows you to register your IP as a derivative of another, based on the License Terms defined in the token View the smart contract for License Tokens. A **License Token** is represented as an **ERC-721 NFT** and contains the specific [License Terms](/concepts/licensing-module/license-terms) it represents. Its associated `licenseTokenId` is global, as there is one License Token contract. Once License Terms are attached to an IP Asset, it becomes public so that anyone can mint a License Token for those terms. A License Token is burned when it is used to register another IP as a derivative of the original IP Asset. A diagram showing what happens when a License Token is minted. ## Private Licenses A diagram showing how private licenses are minted. In order to mint a private License Token, the owner of a root IP Asset can issue License Tokens that have terms **not yet attached to the IP Asset itself**. It is important to also note that derivative IP Assets cannot issue private licenses because it is restricted to only issue licenses of its inherited terms. ## Transferability of the License Token License Tokens might be transferrable or not, depending on the values of the License Terms terms they point to. Once a non-transferable License Token is minted to a recipient, it is locked there forever. ## Registering a Derivative You can register an IP Asset as a derivative of other IP Assets, each with their own license terms agreement. This creates a legally binding agreement between IP Assets that enforces things things like automatic payments in the [💸 Royalty Module](/concepts/royalty-module/overview). ### ⚠️ Restrictions There are a few restrictions on registering a derivative: * An IP Asset can only register as a derivative one time. If an IP Asset has multiple parents, it must register both at the same time. * Once an IP Asset is a derivative, it cannot link any more parents. * When you link an IP Asset as a derivative, it cannot have license terms attached. It will inherit its terms from its parents. * None of the parent IP Assets or the child IP Asset can be disputed. * The child IP Asset cannot have derivatives already. * If at least one of the license terms is commercial, then they all must be commercial (`commercialUse = true`) *** There are two ways to register a derivative IP Asset. An IP Asset can only register as a derivative one time. If an IP Asset has multiple parents, it must register both at the same time. Once an IP Asset is a derivative, it cannot link any more parents. ### 1. Using an Existing License Token A License Token is burned when it is used to register another IP as a derivative of the original IP Asset. Using an Existing License Token ### 2. Registering a Derivative Directly You can also register a derivative directly, without the need for a License Token. Remember that if License Terms are attached to an IP Asset it is public to mint the License Token anyway, so this is simply a convenient way to go about it, thus skipping the middle step of minting a License Token. Registering a Derivative Directly # 📜 Licensing Module Source: https://docs.story.foundation/concepts/licensing-module/overview Learn about creating & attaching real legal license to your IP on Story The Licensing Module allows you to create a real legal license from a **License Template** (which is the [Programmable IP License (PIL💊)](/concepts/programmable-ip-license/overview)) and attach it to your IP Asset. This license, and the **License Terms** that define it, restrict how others can use your IP, commercialize it, and remix it. If License Terms are attached to an IP Asset, anyone can mint a **License Token** (an ERC-721 NFT) from it which acts as the license to use that work based on the terms that define it. This token can then be burned to register a derivative work. This then establishes a parent-child relationship between assets, unlocking things like automatic royalty flow from the [💸 Royalty Module](/concepts/royalty-module/overview). The owner of an IP Asset owns intellectual property rights such as creating derivatives, being commercially exploited, and being reproduced in different platforms. IP Assets can programmatically grant permissions for any users to exercise those rights with some autonomy via [License Tokens](/concepts/licensing-module/license-token) (an ERC-721 NFT), which point to a particular set of conditions, known as [License Terms](/concepts/licensing-module/license-terms). The contracts in blue are built into the protocol. The contracts in white can be developed by the community or 3rd party vendor. ## LicensingModule View the smart contract for the License Module. The `LicensingModule.sol` contract is the main entry point for the licensing system. It is responsible for: * Attaching License Terms to IP Assets * Minting License Tokens * Registering derivatives * Setting License Configs ## Further Readings The following document will walk through all of the major components of the Licensing Module as shown above: * [License Template](/concepts/licensing-module/license-template) * [License Terms](/concepts/licensing-module/license-terms) * [License Token](/concepts/licensing-module/license-token) * [License Registry](/concepts/registry/license-registry) * [License Config](/concepts/licensing-module/license-config) # 👀 Metadata Module Source: https://docs.story.foundation/concepts/metadata-module Manage & view metadata for IP Assets. The Metadata Module enables the creation, management, and retrieval of metadata for IP Assets within Story. It consists of two main components: the CoreMetadataModule for writing operations and the CoreMetadataViewModule for reading operations. View the smart contract for the Core Metadata Module. View the smart contract for the Core Metadata View Module. ## Metadata Structure The metadata for an IP Asset includes: * **metadataURI**: A URI pointing to the detailed metadata of the IP Asset * **metadataHash**: A hash of the metadata for verification purposes * **nftTokenURI**: A URI pointing to the metadata of the NFT associated with the IP Asset * **nftMetadataHash**: A hash of the NFT metadata for verification * **registrationDate**: When the IP Asset was registered * **owner**: The current owner of the IP Asset ## CoreMetadataModule (Write Operations) `CoreMetadataModule.sol` is responsible for writing and updating metadata for IP Assets. It is **stateful** and provides the following key functionalities: * Setting and updating metadata URIs for IP Assets * Setting and updating NFT token URIs * Freezing metadata to make it immutable * Managing metadata hashes for verification The module stores metadata in the IP Asset's storage, making it accessible to other modules and applications. ### Setting Metadata To set metadata for an IP Asset, the caller must have appropriate permissions. The CoreMetadataModule provides several functions for setting metadata: * `setMetadataURI`: Sets just the IP metadataURI and its hash * `updateNftTokenURI`: Updates the NFT token URI and its hash * `setAll`: Sets all metadata attributes at once Here is an example: ```solidity solidity theme={null} // Set the metadata URI and hash coreMetadataModule.setMetadataURI( ipAssetAddress, "https://example.com/metadata/asset123", keccak256("metadata content hash") ); ``` ### Freezing Metadata The CoreMetadataModule allows IP Asset owners to freeze metadata, making it immutable. Once frozen, the metadata cannot be changed, ensuring the permanence of the IP Asset's information. To freeze metadata: ```solidity solidity theme={null} // Make the metadata immutable coreMetadataModule.freezeMetadata(ipAssetAddress); ``` You can check if metadata is frozen using: ```solidity solidity theme={null} // Check if metadata is frozen bool isFrozen = coreMetadataModule.isMetadataFrozen(ipAssetAddress); ``` ## CoreMetadataViewModule (Read Operations) `CoreMetadataViewModule.sol` is a read-only module that provides access to the metadata stored by the CoreMetadataModule. It follows the View Module pattern and offers these key functionalities: * Retrieving metadata URIs and hashes * Retrieving NFT token URIs and metadata hashes * Generating formatted JSON strings with all metadata attributes * Checking registration dates and ownership information ### Retrieving Metadata The CoreMetadataViewModule provides various functions to retrieve metadata: * `getCoreMetadata`: Returns all metadata in a single struct * `getMetadataURI`: Returns just the metadata URI * `getNftTokenURI`: Returns the NFT token URI * `getJsonString`: Returns a formatted JSON string with all metadata Here is an example: ```solidity solidity theme={null} // Get the metadata URI string memory uri = coreMetadataViewModule.getMetadataURI(ipAssetAddress); // Get all metadata in one call CoreMetadata memory metadata = coreMetadataViewModule.getCoreMetadata(ipAssetAddress); // Get a JSON representation of all metadata string memory jsonMetadata = coreMetadataViewModule.getJsonString(ipAssetAddress); ``` The Metadata Module provides a robust system for managing IP Asset metadata, ensuring that important information about intellectual property is properly recorded, accessible, and can be made immutable when needed. # 🧱 Modules Source: https://docs.story.foundation/concepts/modules/overview Learn about the standalone contracts that take action on IP Assets Modules are standalone contracts that adhere to the [`IModule` interface](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/interfaces/modules/base/IModule.sol). These modules play a crucial role in taking action on IP to change the data/state around or of IP. ## Existing Modules There are a few important modules, created by the Story team, to be aware of: | Module | Description | | ------------------------------------------------- | -------------------------------------------------------------------------------------- | | [📜 Licensing Module](/concepts/licensing-module) | Responsible for defining and attaching licenses to IP Assets. | | [💸 Royalty Module](/concepts/royalty-module) | Responsible for handling royalty flow between parent & child IP Assets. | | [❌ Dispute Module](/concepts/dispute-module) | Responsible for handling the dispute of wrongfully registered or misbehaved IP Assets. | | [👥 Grouping Module](/concepts/grouping-module) | Responsible for handling groups of IPAs. | | [👀 Metadata Module](/concepts/metadata-module) | Manage and view metadata for IP Assets. | ## Base Module The Base Module provides a standard set of must-have functionalities for all modules registered on Story. Anyone wishing to create and register a module on Story must inherit and override the Base Module. View the smart contract [here](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/modules/BaseModule.sol). ### Key Features #### Simplicity and Flexibility The BaseModule is intentionally kept simple and generalized. It only implements the ERC165 interface, which is crucial for interface detection. This design choice allows for maximum flexibility when developing more specific modules within Story. #### ERC165 Interface Implementation By implementing the ERC165 interface, BaseModule allows other contracts to query whether it supports a specific interface. This feature is essential for ensuring compatibility and interoperability within the Story ecosystem and beyond. ```solidity theme={null} abstract contract BaseModule is ERC165, IModule { ... } ``` #### `supportsInterface` Function A key function in the BaseModule is `supportsInterface`, which overrides the ERC165's `supportsInterface` method. This function is crucial for interface detection, allowing the contract to declare support for both its own `IModule` interface and any other interfaces it might inherit. ```solidity theme={null} function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId); } ``` # Overview Source: https://docs.story.foundation/concepts/overview A broad overview of Story's "Proof-of-Creativity" protocol. A piece of Intellectual Property is represented as an [🧩 IP Asset](/concepts/ip-asset) and its associated [⚙️ IP Account](/concepts/ip-asset/ip-account), a smart contract designed to serve as the core identity for each IP. We also have various [🧱 Modules](/concepts/modules) to add functionality to IP Assets, like creating derivatives of them, disputing IP, and automating revenue flow between them. Let's briefly introduce the layers mentioned in the above diagram: ## [🧩 IP Asset](/concepts/ip-asset) When you want to bring an IP on-chain, you mint an ERC-721 NFT. This NFT represents **ownership** over your IP. Then, you **register** the NFT in our protocol through the [IP Asset Registry](/concepts/registry/ip-asset-registry). This deploys an [⚙️ IP Account](/concepts/ip-asset/ip-account), effectively creating an "IP Asset". The address of that contract is the identifier for the IP Asset (the `ipId`). The underlying NFT can be traded/sold like any other NFT, and the new owner will own the IP Asset and all revenue associated with it. ## [⚙️ IP Account](/concepts/ip-asset/ip-account) IP Accounts are smart contracts that are tied to an IP Asset, and do two main things: 1. Store the associated IP Asset's data, such as the associated licenses and royalties created from the IP 2. Facilitates the utilization of this data by various modules. For example, licensing, revenue/royalty sharing, remixing, and other critical features are made possible due to the IP Account's programmability. The address of the IP Account is the IP Asset's identifier (the `ipId`). ## [🧱 Modules](/concepts/modules) Modules are customizable smart contracts that define and extend the functionality of IP Accounts. Modules empower developers to create functions and interactions for each IP to make IPs truly programmable. We already have a few core modules: 1. [📜 Licensing Module](/concepts/licensing-module): create parent\<->child relationships between IPs, enabling derivatives of IPs that are restricted by the agreements in the license terms (must give attribution, share 10% revenue, etc) 2. [💸 Royalty Module](/concepts/royalty-module): automate revenue flow between IPs, abiding by the negotiated revenue sharing in license terms 3. [❌ Dispute Module](/concepts/dispute-module): facilitates the disputing and flagging of IP 4. [👥 Grouping Module](/concepts/grouping-module): allows for IPs to be grouped together 5. [👀 Metadata Module](/concepts/metadata-module): manage and view metadata for IP Assets ## [🗂️ Registry](/concepts/registry) The various registries on our protocol function as a primary directory/storage for the global states of the protocol. Unlike IP Accounts, which manage the state of specific IPs, a registry oversees the broader states of the protocol. ## [💊 Programmable IP License (PIL)](/concepts/programmable-ip-license) The PIL is a real, off-chain legal contract that defines certain **License Terms** for how an IP Asset can be legally licensed. For example, how an IP Asset is commercialized, remixed, or attributed, and who is allowed to do that and under what conditions. We have mapped these same terms on-chain so you can easily attach terms to your IP Asset for others to seamlessly and transparently license your IP. # How does Story protect IP? Source: https://docs.story.foundation/concepts/programmable-ip-license/how-does-story-protect-ip Okay... how does Story *actually* protect IP? How Does Story Protect IP? Every license created on Story is a real, enforceable legal contract. ## The Programmable IP License (PIL): The Foundation of Legal Enforcement At its core, every [IP Asset](/concepts/ip-asset) registered on Story is wrapped by a **legally binding document called the [Programmable IP License (PIL)](/concepts/programmable-ip-license)**. Based on US copyright law, the PIL acts as a universal license agreement template that allows IP owners to attach customizable terms to their assets. **Licensing on Story means making a genuine legal commitment.** The parameters defined in the PIL—commercial use, derivative allowances, attribution requirements, and royalty structures—represent legally enforceable terms between the IP owner (licensor) and anyone who licenses the IP (licensee). ### How does the PIL enable enforcement? * **Clear Legal Terms:** The PIL provides a standardized way for IP owners to define usage rules. * **On-Chain Record as Evidence:** PIL terms attached to an IP Asset are recorded immutably on Story's purpose-built blockchain, serving as *irrefutable evidence* of the agreed-upon terms. * **Off-Chain Legal Recourse:** If IP is misused in violation of PIL terms, the IP owner can leverage on-chain evidence in **off-chain legal proceedings** such as arbitration or court actions. * **License Tokens as Proof of Rights:** Licensees receive a [**License Token**](/concepts/licensing-module/license-token) (NFT) representing specific usage rights granted under the PIL terms, providing further evidence of authorization status. ## The Story Attestation Service (SAS): Proactive Infringement Monitoring Beyond the legal framework, we are building the [**Story Attestation Service (SAS)**](/concepts/story-attestation-service) to help IP owners monitor for potential copyright infringement using a multi-layered decentralized approach. SAS is a signal layer, not a judgment layer—it flags potential issues for the IP owner to act upon, rather than taking automated enforcement actions. ### How the SAS Helps with Infringement Detection: * **Network of Specialized Providers:** SAS coordinates with service providers like Yakoa and Pex that use AI and machine learning to detect copyright violations across different media types on the internet and other blockchains. * **Transparent Signals:** SAS provides publicly accessible signals regarding the legitimacy of an IP Asset based on provider findings. * **Focus on Commercial IP:** Currently, SAS primarily runs infringement checks on commercial IP Assets—those with at least one License Terms where `commercialUse = true`. * **Metadata-Driven Checks:** SAS relies on IP-specific metadata provided during registration to perform checks against existing online content. ### Important Considerations: * **Detection, Not Prevention:** SAS primarily flags potential infringements after IP registration rather than preventing them. * **Internet-Based Checks:** Currently, SAS primarily detects infringement based on content already existing online, not offline uses. * **No Guarantee of Perfection:** No system can guarantee 100% detection of all copyright infringement. ## The Role of the Dispute Module We have also built a [**Dispute Module**](/concepts/dispute-module) that allows anyone to raise on-chain disputes against IP Assets for reasons such as improper registration or potential plagiarism. This can lead to on-chain flagging of disputed IP, potentially affecting its ability to generate licenses or earn revenue. ## The Hybrid Enforcement Model Story doesn't replace courts or lawyers—it gives IP holders tools that work with traditional enforcement systems while benefiting from on-chain automation, transparency, and interoperability. ### What Story Can Do: * Provide a legally sound framework for IP licensing through the PIL * Create an immutable on-chain record of IP ownership and license terms * Offer monitoring tools through the SAS to detect potential online infringement * Facilitate on-chain dispute resolution through the Dispute Module * Provide evidence usable for off-chain legal enforcement ### What Story Cannot Do: * Act as a global police force for IP infringement * Guarantee prevention of all unauthorized IP uses * Directly enforce legal judgments in the physical world * Monitor every digital and physical interaction with registered IP ``` +--------------------------+ +-----------------------------+ | IP Owner Registers IP on |----->| IP Asset Created on Story | | Story | | (with associated metadata) | +--------------------------+ +-----------------------------+ | v +---------------------------------------+ +-------------------------+ | Programmable IP License (PIL) |<--| IP Owner Attaches Legal | | (Legal wrapper defining usage terms) | | Terms via PIL | +---------------------------------------+ +-------------------------+ | v +-------------------------+ | IP Asset with PIL Terms | | (Commercial Use = true) | +-------------------------+ | v +--------------------------+ +-------------------------------------+ | Story Attestation |----->| SAS Providers Scan Internet & Other | | Service (SAS) Coordinates| | Sources for Infringement (using IP | +--------------------------+ | Metadata) | +-------------------------------------+ | v +----------------------------------------------------------------------+ | SAS Providers Report Potential Infringement Signals for the IP Asset | | (e.g., "Potential copy found on website X") | +----------------------------------------------------------------------+ | v +---------------------------------------------------------------------+ | IP Owner Reviews SAS Signals on IP Portal (Coming Soon) | +---------------------------------------------------------------------+ | v +---------------------------------------------------------------------+ | IP Owner Can Use SAS Signals & PIL Terms as Evidence for: | | - On-Chain Dispute via Dispute Module | | - Off-Chain Legal Action (e.g., Cease & Desist, Lawsuit) | +---------------------------------------------------------------------+ ``` # 💊 Programmable IP License (PIL) Source: https://docs.story.foundation/concepts/programmable-ip-license/overview Story Programmable IP License - A legal framework for IP licensing on-chain The PIL is a legal off-chain document based on US copyright law created by the Story team. The parameters outlined in the PIL (ex. "Commercial Use", "Derivatives Allowed", etc) have been mapped on-chain, which means they can be enforced on-chain via our protocol, bridging code and law and unlocking the benefit of transparent, autonomous, and permission-less smart contracts for the world of intellectual property. Check out the actual PIL legal text. It is very human-readable for a legal text! The PIL is the first and currently only example of a [License Template](/concepts/licensing-module/license-template). A License Template is simply a traditional legal document that has been brought on-chain and contains a set of pre-defined terms that people must set, like: * `commercialUse` - can someone use my work commercially? * `mintingFee` - the cost of minting a license to use my work in your own works. * `derivativesAttribution` - does someone have to credit me in their derivative works? In code, these terms form a struct that represent their legal off-chain counterparts. To see all of the terms defined by the PIL and their associated explanations in code, see [PIL Terms](/concepts/programmable-ip-license/pil-terms). To see example configurations ("flavors") of the PIL, see [PIL Flavors (examples)](/concepts/programmable-ip-license/pil-flavors). ## The Background Story If you just want to get started developing with the PIL, you can skip this section. We designed Story's [📜 Licensing Module](/concepts/licensing-module/overview) to power the expansion of emerging forms of creativity, such as authorized remixes and co-creation. Our protocol can support any media format or project, ranging from user-generated social videos & images to Hollywood-grade collaborative storytelling. Intellectual property owners can permit other parties to use, or build on, their work by granting rights in a license, which can be for profit or for the common good. In the media world, these licenses are generally highly tailored contracts, which vary by media formats and the unique needs of licensors - often requiring unique expertise (via lawyers) and significant resources to create. We searched for a form of a "universal license" that could support these emerging activities at scale. Hat tip to [Creative Commons](https://creativecommons.org/mission/), [Arweave](https://mirror.xyz/0x64eA438bd2784F2C52a9095Ec0F6158f847182d9/AjNBmiD4A4Sw-ouV9YtCO6RCq0uXXcGwVJMB5cdfbhE), A16Z / [Can't Be Evil,](https://a16zcrypto.com/posts/article/introducing-nft-licenses/) The [Token-Bound NFT License](https://james.grimmelmann.net/files/articles/token-bound-nft-license.pdf) and music rights organizations, among others. But we simply couldn't find one framework or agreement robust enough - so with our expert legal counsel (with special thanks to Ghaith Mahmood and Heather Liu) we created one ourselves! **Introducing the Programmable IP License (PIL:pill:)**, the first example of a [License Template](/concepts/licensing-module/license-template) on the protocol. ## Feedback We are excited to collect feedback and collaborate with IP owners to unlock the potential of their works - please let us know what you think! We can be reached at `legal@storyprotocol.xyz`. Check out the actual PIL legal text. It is very human-readable for a legal text! # PIL Flavors (examples) Source: https://docs.story.foundation/concepts/programmable-ip-license/pil-flavors Pre-configured License Terms for ease of use The [💊 Programmable IP License (PIL)](/concepts/programmable-ip-license/overview) is very configurable, but we support popular pre-configured License Terms (also known as "flavors") for ease of use. We expect these to be the most popular options: PIL Flavor Comparison ## Non-Commercial Social Remixing This flavor is already registered as `licenseTermsId = 1` on our protocol. This is because it doesn't take any inputs, so we registered it ahead of time. Let the world build on and play with your creation. This license allows for endless free remixing while tracking all uses of your work while giving you full credit. Similar to: TikTok plus attribution. ### What others can do? | Others can | Others cannot | | ----------------------------------------------------- | ----------------------------------------------------------------------------------- | | ✅ Remix this work (`derivativesAllowed == true`) | ❌ Commercialize the original and derivative works (`commercialUse == false`) | | ✅ Distribute their remix anywhere | ❌ Claim credit for any derivative works (`derivativesAttribution == true`) | | ✅ Get the license for free (`defaultMintingFee == 0`) | ❌ Claim credit for the original work ("Attribution" is true in the off-chain terms) | ### PIL Term Values * **On-chain**: ```solidity Solidity theme={null} PILTerms({ transferable: true, royaltyPolicy: address(0), defaultMintingFee: 0, expiration: 0, commercialUse: false, commercialAttribution: false, commercializerChecker: address(0), commercializerCheckerData: EMPTY_BYTES, commercialRevShare: 0, commercialRevCeiling: 0, derivativesAllowed: true, derivativesAttribution: true, derivativesApproval: false, derivativesReciprocal: true, derivativeRevCeiling: 0, currency: address(0), uri: "https://github.com/piplabs/pil-document/blob/998c13e6ee1d04eb817aefd1fe16dfe8be3cd7a2/off-chain-terms/NCSR.json" }); ``` ```typescript TypeScript theme={null} import { zeroAddress } from "viem"; import { LicenseTerms } from "@story-protocol/core-sdk"; const nonCommercialSocialRemix: LicenseTerms = { transferable: true, royaltyPolicy: zeroAddress, defaultMintingFee: 0n, expiration: 0n, commercialUse: false, commercialAttribution: false, commercializerChecker: zeroAddress, commercializerCheckerData: "0x", commercialRevShare: 0, commercialRevCeiling: 0n, derivativesAllowed: true, derivativesAttribution: true, derivativesApproval: false, derivativesReciprocal: true, derivativeRevCeiling: 0n, currency: zeroAddress, uri: "https://github.com/piplabs/pil-document/blob/998c13e6ee1d04eb817aefd1fe16dfe8be3cd7a2/off-chain-terms/NCSR.json", }; ``` * **Off-chain:** | Parameter | Options / Tags | | --------------------------------- | --------------------------------------------------------------------------- | | Territory | No restrictions | | Channels of Distribution | No Restriction | | Attribution | True | | Content Standards | No-Hate, Suitable-for-All-Ages, No-Drugs-or-Weapons, No-Pornography | | Sublicensable | False | | AI Learning Models | False | | Restriction on Cross-Platform Use | False | | Governing Law | California, USA | | Alternative Dispute Resolution | Tag: Alternative-Dispute-Resolution Ledger-Authoritative-Dispute-Resolution | | Additional License Parameters | None | ## Commercial Use Retain control over reuse of your work, while allowing anyone to appropriately use the work in exchange for the economic terms you set. This is similar to Shutterstock with creator-set rules. ### What others can do? | Others can | Others cannot | | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | ✅ Commercialize the original work (`commercialUse == true`) | ❌ Remix this work (`derivativesAllowed == false`) | | ✅ Keep all revenue (`commercialRevShare == 0`) | ❌ Claim credit for the original work (`commercialAttribution == true`) | | | ❌ Get the license for free (`defaultMintingFee` is set) | | | ❌ Claim credit for the original work even non-commercially ("Attribution" is true in the off-chain terms) | ### PIL Term Values * **On-chain**: ```solidity Solidity theme={null} PILTerms({ transferable: true, royaltyPolicy: ROYALTY_POLICY, // ex. RoyaltyPolicyLAP address defaultMintingFee: MINTING_FEE, // ex. 1000000000000000000 (which means it costs 1 $WIP to mint) expiration: 0, commercialUse: true, commercialAttribution: true, commercializerChecker: address(0), commercializerCheckerData: EMPTY_BYTES, commercialRevShare: 0, commercialRevCeiling: 0, derivativesAllowed: false, derivativesAttribution: false, derivativesApproval: false, derivativesReciprocal: false, derivativeRevCeiling: 0, currency: CURRENCY, // ex. $WIP address uri: "https://github.com/piplabs/pil-document/blob/9a1f803fcf8101a8a78f1dcc929e6014e144ab56/off-chain-terms/CommercialUse.json" }) ``` ```typescript TypeScript theme={null} import { zeroAddress, parseEther } from "viem"; import { LicenseTerms } from "@story-protocol/core-sdk"; const commercialUse: LicenseTerms = { transferable: true, royaltyPolicy: ROYALTY_POLICY, // ex. RoyaltyPolicyLAP address defaultMintingFee: MINTING_FEE, // ex. parseEther("1") (which means it costs 1 $WIP to mint) expiration: 0n, commercialUse: true, commercialAttribution: true, commercializerChecker: zeroAddress, commercializerCheckerData: "0x", commercialRevShare: 0, commercialRevCeiling: 0n, derivativesAllowed: false, derivativesAttribution: false, derivativesApproval: false, derivativesReciprocal: false, derivativeRevCeiling: 0n, currency: CURRENCY, // ex. $WIP address uri: "https://github.com/piplabs/pil-document/blob/9a1f803fcf8101a8a78f1dcc929e6014e144ab56/off-chain-terms/CommercialUse.json", }; ``` * **Off-chain** | Parameter | Options / Tags | | --------------------------------- | --------------------------------------------------------------------------- | | Territory | No restrictions | | Channels of Distribution | No Restriction | | Attribution | True | | Content Standards | No-Hate, Suitable-for-All-Ages, No-Drugs-or-Weapons, No-Pornography | | Sublicensable | False | | AI Learning Models | False | | Restriction on Cross-Platform Use | False | | Governing Law | California, USA | | Alternative Dispute Resolution | Tag: Alternative-Dispute-Resolution Ledger-Authoritative-Dispute-Resolution | | Additional License Parameters | None | ## Commercial Remix Let the world build on and play with your creation... and earn money together from it! This license allows for endless free remixing while tracking all uses of your work while giving you full credit, with each derivative paying a percentage of revenue to its "parent" IP. ### Example Check out Story's official mascot **Ippy**, which we have registered with commercial remix terms on both [Mainnet](https://explorer.story.foundation/ipa/0xB1D831271A68Db5c18c8F0B69327446f7C8D0A42) and [Aeneid Testnet](https://aeneid.explorer.story.foundation/ipa/0x641E638e8FCA4d4844F509630B34c9D524d40BE5). ### What others can do? | Others can | Others cannot | | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | ✅ Remix this work (`derivativesAllowed == true`) | ❌ Claim credit for the original work (`commercialAttribution == true`) | | ✅ Commercialize the original and derivative works (`commercialUse == true`) | ❌ Claim credit for any derivative works (`derivativesAttribution == true`) | | ✅ Distribute their remix anywhere | ❌ Keep all revenue (`commercialRevShare` is set) | | | ❌ Get the license for free (`defaultMintingFee` is set) | | | ❌ Claim credit for the original work even non-commercially ("Attribution" is true in the off-chain terms) | ### PIL Term Values * **On-chain**: ```solidity Solidity theme={null} PILTerms({ transferable: true, royaltyPolicy: ROYALTY_POLICY, // ex. RoyaltyPolicyLAP address defaultMintingFee: MINTING_FEE, // ex. 1000000000000000000 (which means it costs 1 $WIP to mint) expiration: 0, commercialUse: true, commercialAttribution: true, commercializerChecker: address(0), commercializerCheckerData: EMPTY_BYTES, commercialRevShare: COMMERCIAL_REV_SHARE, // ex. 50 * 10 ** 6 (which means 50% of derivative revenue) commercialRevCeiling: 0, derivativesAllowed: true, derivativesAttribution: true, derivativesApproval: false, derivativesReciprocal: true, derivativeRevCeiling: 0, currency: CURRENCY, // ex. $WIP address uri: "https://github.com/piplabs/pil-document/blob/ad67bb632a310d2557f8abcccd428e4c9c798db1/off-chain-terms/CommercialRemix.json" }); ``` ```typescript TypeScript theme={null} import { zeroAddress, parseEther } from "viem"; import { LicenseTerms } from "@story-protocol/core-sdk"; const commercialRemix: LicenseTerms = { transferable: true, royaltyPolicy: ROYALTY_POLICY, // ex. RoyaltyPolicyLAP address defaultMintingFee: MINTING_FEE, // ex. parseEther("1") (which means it costs 1 $WIP to mint) expiration: 0n, commercialUse: true, commercialAttribution: true, commercializerChecker: zeroAddress, commercializerCheckerData: "0x", commercialRevShare: COMMERCIAL_REV_SHARE, // ex. 50 (which means 50% of derivative revenue) commercialRevCeiling: 0n, derivativesAllowed: true, derivativesAttribution: true, derivativesApproval: false, derivativesReciprocal: true, derivativeRevCeiling: 0n, currency: CURRENCY, // ex. $WIP address uri: "https://github.com/piplabs/pil-document/blob/ad67bb632a310d2557f8abcccd428e4c9c798db1/off-chain-terms/CommercialRemix.json", }; ``` * **Off-chain** | Parameter | Options / Tags | | --------------------------------- | --------------------------------------------------------------------------- | | Territory | No restrictions | | Channels of Distribution | No Restriction | | Attribution | True | | Content Standards | No-Hate, Suitable-for-All-Ages, No-Drugs-or-Weapons, No-Pornography | | Sublicensable | False | | AI Learning Models | False | | Restriction on Cross-Platform Use | False | | Governing Law | California, USA | | Alternative Dispute Resolution | Tag: Alternative-Dispute-Resolution Ledger-Authoritative-Dispute-Resolution | | Additional License Parameters | None | ## Creative Commons Attribution Let the world build on and play with your creation - including making money. ### What others can do? | Others can | Others cannot | | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | ✅ Remix this work (`derivativesAllowed == true`) | ❌ Claim credit for the original work (`commercialAttribution == true`) | | ✅ Commercialize the original and derivative works (`commercialUse == true`) | ❌ Claim credit for any derivative works (`derivativesAttribution == true`) | | ✅ Distribute their remix anywhere | ❌ Claim credit for the original work even non-commercially ("Attribution" is true in the off-chain terms) | | ✅ Get the license for free (`defaultMintingFee == 0`) | | | ✅ Keep all revenue (`commercialRevShare == 0`) | | ### PIL Term Values * **On-chain**: ```solidity Solidity theme={null} PILTerms({ transferable: true, royaltyPolicy: ROYALTY_POLICY, // ex. RoyaltyPolicyLAP address defaultMintingFee: 0, expiration: 0, commercialUse: true, commercialAttribution: true, commercializerChecker: address(0), commercializerCheckerData: EMPTY_BYTES, commercialRevShare: 0, commercialRevCeiling: 0, derivativesAllowed: true, derivativesAttribution: true, derivativesApproval: false, derivativesReciprocal: true, derivativeRevCelling: 0, currency: CURRENCY, // ex. $WIP address uri: 'https://github.com/piplabs/pil-document/blob/998c13e6ee1d04eb817aefd1fe16dfe8be3cd7a2/off-chain-terms/CC-BY.json' }); ``` ```typescript TypeScript theme={null} import { zeroAddress } from "viem"; import { LicenseTerms } from "@story-protocol/core-sdk"; const creativeCommonsAttribution: LicenseTerms = { transferable: true, royaltyPolicy: ROYALTY_POLICY, // ex. RoyaltyPolicyLAP address defaultMintingFee: 0n, expiration: 0n, commercialUse: true, commercialAttribution: true, commercializerChecker: zeroAddress, commercializerCheckerData: "0x", commercialRevShare: 0, commercialRevCeiling: 0n, derivativesAllowed: true, derivativesAttribution: true, derivativesApproval: false, derivativesReciprocal: true, derivativeRevCelling: 0n, currency: CURRENCY, // ex. $WIP address uri: "https://github.com/piplabs/pil-document/blob/998c13e6ee1d04eb817aefd1fe16dfe8be3cd7a2/off-chain-terms/CC-BY.json", }; ``` * **Off-chain** | Parameter | Options / Tags | | --------------------------------- | --------------------------------------------------------------------------- | | Territory | No restrictions | | Channels of Distribution | No Restriction | | Attribution | True | | Content Standards | No-Hate, Suitable-for-All-Ages, No-Drugs-or-Weapons, No-Pornography | | Sublicensable | False | | AI Learning Models | True | | Restriction on Cross-Platform Use | False | | Governing Law | California, USA | | Alternative Dispute Resolution | Tag: Alternative-Dispute-Resolution Ledger-Authoritative-Dispute-Resolution | | Additional License Parameters | None | # Examples Here are some common examples of royalty flow. *More coming soon!* ## Example 1 Example 1 Royalty Flow ### Explanation Someone registers their Azuki on Story. By default, that IP Asset has Non-Commercial Social Remixing Terms, which specify that anyone can create derivatives of that work but cannot commercialize them. So, someone else creates & registers a remix of that work (IPA2) which inherits those same terms. Someone else then does the same to IPA2, creating & registering IPA3. The owner of IPA1 then decides that others can commercialize the work, but they cannot create derivatives to do so, they must pay a 10 \$WIP minting fee, and they must share 10% of all revenue earned. So, someone wants to commercialize IPA1 by putting it on a t-shirt. They pay the 10 \$WIP minting fee to get a License Token, which represents the license to commercialize IPA1. They then put the image on a t-shirt and sell it. 10% of revenue earned by that t-shirt must be sent on-chain to IPA1. ## Example 2 Example 2 Royalty Flow ### Explanation Someone registers their Azuki on Story. By default, that IP Asset has Non-Commercial Social Remixing Terms, which specify that anyone can create derivatives of that work but cannot commercialize them. So, someone else creates & registers a remix of that work (IPA2) which inherits those same terms. Someone else then does the same to IPA2, creating & registering IPA3. The owner of IPA1 then decides that others can create derivatives of their work and commercialize them, but they must pay a 10 \$WIP minting fee and share 10% of all revenue earned. So, someone wants to commercialize IPA1 by putting it on a t-shirt. They pay the 10 \$WIP minting fee to get a License Token and burn it to create their own derivative, which changes the background color to red. They then put the remixed image on a t-shirt and sell it. 10% of revenue earned by that t-shirt must be sent on-chain to IPA1. A third person wants to commercialize the remix by putting it in a TV advertisement, but they want to change the hair color to white. So, they pay a 10 \$WIP minting fee (of which, 1 \$WIP gets sent back to IPA1) to create their own derivative. They then put the remixed image in a TV ad. 10% of TV advertising revenue earned must be sent on-chain to IPA4, of which 10% will be distributed back to IPA1. # PIL Terms Source: https://docs.story.foundation/concepts/programmable-ip-license/pil-terms Detailed explanation of all terms available in the Programmable IP License If you haven't already, read the Programmable IP License (PIL💊) overview. Since there are so many possible combinations of the PIL, we have created preset "flavors" for you to use while developing. Check out the actual PIL legal text. It is very human-readable for a legal text! # On-chain terms Most PIL terms are on-chain. They are implemented in the `IPILicenseTemplate.sol` contract as a `PILTerms` struct [here](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/interfaces/modules/licensing/IPILicenseTemplate.sol). ```solidity IPILicenseTemplate.sol theme={null} /// @notice This struct defines the terms for a Programmable IP License (PIL). /// These terms can be attached to IP Assets. struct PILTerms { bool transferable; address royaltyPolicy; uint256 defaultMintingFee; uint256 expiration; bool commercialUse; bool commercialAttribution; address commercializerChecker; bytes commercializerCheckerData; uint32 commercialRevShare; uint256 commercialRevCeiling; bool derivativesAllowed; bool derivativesAttribution; bool derivativesApproval; bool derivativesReciprocal; uint256 derivativeRevCeiling; address currency; string uri; } ``` ## Descriptions | Parameter | Values | Description | | --------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `transferable` | True/False | If false, the License Token cannot be transferred once it is minted to a recipient address. | | `royaltyPolicy` | Address | The address of the royalty policy contract. | | `defaultMintingFee` | # | The fee to be paid when minting a license. | | `expiration` | # | The expiration period of the license. | | `commercialUse` | True/False | You can make money from using the original IP Asset, subject to limitations below. | | `commercialAttribution` | True/False | If true, people must give credit to the original work in their commercial application (eg. merch) | | `commercializerChecker` | Address | Commercializers that are allowed to commercially exploit the original work. If zero address, then no restrictions are enforced. | | `commercializerCheckerData` | Bytes | The data to be passed to the commercializer checker contract. | | `commercialRevShare` | \[0-100,000,000] | Amount of revenue (from any source, original & derivative) that must be shared with the licensor (a value of 10,000,000 == 10% of revenue share). This will collect all revenue from tokens that are whitelisted in the [RoyaltyModule.sol contract](https://github.com/storyprotocol/protocol-core-v1/blob/e339f0671c9172a6699537285e32aa45d4c1b57b/contracts/modules/royalty/RoyaltyModule.sol#L50). | | `commercialRevCeiling` | # | If `commercialUse` is set to true, this value determines the maximum revenue you can earn from the original work. | | `derivativesAllowed` | True/False | Indicates whether the licensee can create derivatives of his work or not. | | `derivativesAttribution` | True/False | If true, derivatives that are made must give credit to the original work. | | `derivativesApproval` | True/False | If true, the licensor must approve derivatives of the work. | | `derivativesReciprocal` | True/False | If false, you cannot create a derivative of a derivative. Set this to true to allow indefinite remixing. | | `derivativeRevCeiling` | # | If `commercialUse` is set to true, this value determines the maximum revenue you can earn from derivative works. | | `currency` | Address | The ERC20 token to be used to pay the minting fee. The token must be registered on Story. | | `uri` | String | The URI of the license terms, which can be used to fetch [off-chain license terms](/concepts/programmable-ip-license/pil-terms#off-chain-terms-to-be-included-in-uri-field). | # Off-chain terms to be included in `uri` field Some PIL terms must be stored off-chain and passed in the `uri` field above. This is because these terms are often more lengthy and/or descriptive, so it would not make sense to store them on-chain. | Parameter | Description | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `territory` | Limit usage of the IP to certain regions and/or countries. By default, the IP can be used globally. | | `channelsOfDistribution` | Restrict usage of the IP to certain media formats and use in certain channels of distribution. By default, the IP can be used across all possible channels of distribution. Examples: "television", "physical consumer products", "video games", etc. | | `attribution` | If the original author should be credited for usage of the IP. By default, you do not need to provide credit to the original author. | | `contentStandards` | Set content standards around use of the IP. By default, no standards apply. Examples: "No-Hate", "Suitable-for-All-Ages", "No-Drugs-or-Weapons", "No-Pornography". | | `sublicensable` | Derivative works can grant the same rights they received under this license to a 3rd party, without approval from the original licensor. By default, derivatives may not do so. | | `aiLearningModels` | Whether or not the IP can be used to develop AI learning models. By default, the IP **cannot** be used for such development. | | `restrictionOnCrossPlatformUse` | Limit licensing and creation of derivative works solely on the app on which the IP is made available. By default, the IP can be used anywhere. | | `governingLaw` | The laws of a certain jurisdiction by which this license abides. By default, this is California, USA. | | `alternativeDisputeResolution` | Please see section 3.1 (s) [here](https://github.com/piplabs/pil-document/blob/main/pil.pdf). | | `PILUri` | The URI to the PIL legal terms. | | `additionalParameters` | There may be other terms the licensor would like to add and they can do so in this tag. | # Group IP Asset Registry Source: https://docs.story.foundation/concepts/registry/group-ip-asset-registry Learn about the registry responsible for managing Group IP Assets on Story. View the smart contract for the Group IP Asset Registry. The Group IP Asset Registry is responsible for managing the registration and tracking of Group IP Assets, including the group members and reward pools. The Group IP Asset Registry will maintain grouping relationship on-chain between the Group's IP Account and individual IP Accounts through a mapping: ```solidity GroupIPAssetRegistry.sol theme={null} mapping(address groupIpId => EnumerableSet.AddressSet memberIpIds) groups; ``` ### Notable Functions ```solidity GroupIPAssetRegistry.sol theme={null} function registerGroup(address groupNft, uint256 groupNftId, address rewardPool) external onlyGroupingModule whenNotPaused returns (address groupId) ``` This function registers a new Group IPA on Story. ```solidity GroupIPAssetRegistry.sol theme={null} function addGroupMember(address groupId, address[] calldata ipIds) external onlyGroupingModule whenNotPaused ``` Adds already registered IPAs to an existing Group IPA. ```solidity GroupIPAssetRegistry.sol theme={null} function removeGroupMember(address groupId, address[] calldata ipIds) external onlyGroupingModule whenNotPaused ``` Removes registered IPAs from a Group IPA. # IP Asset Registry Source: https://docs.story.foundation/concepts/registry/ip-asset-registry Learn about the registry responsible for registering IPs on Story. View the smart contract for the IP Asset Registry. The IP Asset Registry is responsible for registering IPs into the protocol. It deploys a dedicated [IP Account](/concepts/ip-asset/ip-account) contract for each new IP Asset registered on the protocol (*NOTE: This registry inherits from the* [IP Account Registry](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/registries/IPAccountRegistry.sol)) ### Notable Functions ```solidity IPAssetRegistry.sol theme={null} function register(uint256 chainid, address tokenContract, uint256 tokenId) external whenNotPaused returns (address id) ``` This function registers an ERC-721 NFT as a new IP Asset on Story. # License Registry Source: https://docs.story.foundation/concepts/registry/license-registry Learn about the registry that manages license-related states on Story. View the smart contract for the License Registry. The License Registry stores all license-related states within the protocol, including managing global state like registering new License Templates like the [Programmable IP License (PIL💊)](/concepts/programmable-ip-license/overview), attaching licenses to individual [IP Assets](/concepts/ip-asset/overview), registering derivatives, and the like: ```solidity LicenseRegistry.sol theme={null} /// @dev Storage of the LicenseRegistry /// @param defaultLicenseTemplate The default license template address /// @param defaultLicenseTermsId The default license terms ID /// @param registeredLicenseTemplates Registered license templates /// @param registeredRoyaltyPolicies Registered royalty policies /// @param registeredCurrencyTokens Registered currency tokens /// @param parentIps Mapping of parent IPs to derivative IPs /// @param parentLicenseTerms Mapping of parent IPs to license terms used to link to derivative IPs /// @param childIps Mapping of derivative IPs to parent IPs /// @param attachedLicenseTerms Mapping of attached license terms to IP IDs /// @param licenseTemplates Mapping of license templates to IP IDs /// @param expireTimes Mapping of IP IDs to expire times /// @param licensingConfigs Mapping of minting license configs to a licenseTerms of an IP /// @dev Storage structure for the LicenseRegistry /// @custom:storage-location erc7201:story-protocol.LicenseRegistry struct LicenseRegistryStorage { address defaultLicenseTemplate; uint256 defaultLicenseTermsId; mapping(address licenseTemplate => bool isRegistered) registeredLicenseTemplates; mapping(address childIpId => EnumerableSet.AddressSet parentIpIds) parentIps; mapping(address childIpId => mapping(address parentIpId => uint256 licenseTermsId)) parentLicenseTerms; mapping(address parentIpId => EnumerableSet.AddressSet childIpIds) childIps; mapping(address ipId => EnumerableSet.UintSet licenseTermsIds) attachedLicenseTerms; mapping(address ipId => address licenseTemplate) licenseTemplates; mapping(bytes32 ipLicenseHash => Licensing.LicensingConfig licensingConfig) licensingConfigs; } ``` ### Notable Functions ```solidity LicenseRegistry.sol theme={null} function attachLicenseTermsToIp(address ipId, address licenseTemplate, uint256 licenseTermsId) external onlyLicensingModule ``` This function allows you to attach License Terms to an IP Asset. ```solidity LicenseRegistry.sol theme={null} function registerDerivativeIp(address childIpId, address[] calldata parentIpIds, address licenseTemplate, uint256[] calldata licenseTermsIds, bool isUsingLicenseToken) external onlyLicensingModule ``` This function allows you to register an IP Asset as a derivative of another IP Asset, unlocking things like claimable royalty flows from the [💸 Royalty Module](/concepts/royalty-module/overview). # Module Registry Source: https://docs.story.foundation/concepts/registry/module-registry Learn about the registry that manages modules and hooks on Story. View the smart contract for the Module Registry. The Module Registry maintains and updates the global list of modules and hooks registered permissionlessly on Story. It can enable/disable modules on a per-IP Account basis for granular control over each IP Account's interaction with modules and hooks. **This module is likely not very important for you** unless you wish to dive into creating/reading modules. # 🗂️ Registry Source: https://docs.story.foundation/concepts/registry/overview Learn about the various registries that maintain the global state of Story's protocol. The various registries on Story function as a primary directory/storage for the global states of the protocol. Obviously, they also contain functions to update that storage. Unlike [⚙️ IP Accounts](/concepts/ip-asset/ip-account), which manage the state of specific IPs, a **registry** oversees the broader states of the protocol. # Types of Registries Below are all of the registries on Story. ## [IP Asset Registry](/concepts/registry/ip-asset-registry) Responsible for registering IPs into the protocol. ## [Group IP Asset Registry](/concepts/registry/group-ip-asset-registry) Responsible for registering and maintaining Group IP Assets. ## [License Registry](/concepts/registry/license-registry) Stores all license-related states within the protocol, like attaching License Terms to IP Assets, registering derivatives, creating new License Templates, etc. ## [Module Registry](/concepts/registry/module-registry) Maintains and updates the global list of modules and hooks registered permissionlessly on Story # External Royalty Policies Source: https://docs.story.foundation/concepts/royalty-module/external-royalty-policies Learn about custom royalty policies that can be created for specific use cases There can be many flavors and variations of royalty distribution rules as we observe in the real world. The same can be expected onchain. Whenever a use case requires unique and specific royalty rules, then those set of rules can be registered as an **External Royalty Policy**. ## 1. What is an External Royalty Policy? It is a smart contract that inherits a specific interface called `IExternalRoyaltyPolicy`, which defines the view function below: View the smart contract for external royalty policies. ```solidity IExternalRoyaltyPolicy.sol theme={null} /// @notice Returns the amount of royalty tokens required to link a child to a given IP asset /// @param ipId The ipId of the IP asset /// @param licensePercent The percentage of the license /// @return The amount of royalty tokens required to link a child to a given IP asset function getPolicyRtsRequiredToLink(address ipId, uint32 licensePercent) external view returns (uint32); ``` After developing your smart contract make sure it inherits the interface above and you can register your new External Royalty Policy by calling `registerExternalRoyaltyPolicy` function in [RoyaltyModule.sol](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/modules/royalty/RoyaltyModule.sol). ## 2. How does it work? Let's follow an example of a new External Royalty Policy called "Policy X". ### External Royalty Policies are selected by users An IPA owner decides the royalty policy he/she wants to allow the IP to be remixed with. There are multiple options of royalty rules that can be chosen such as LAP, LRP and other External Royalty Policies. Let's say the user decides to mint a license token with "Policy X". After that, IP2 remixes IP1 and IP3 remixes IP2 and we have the situation as the image below: External Royalty Policy Example Every time there is a remix - the link between the parent and derivative has 2 data points associated: 1. The royalty policy address 1. "Policy X" address in the example 2. The percentage of royalty tokens the parent demands from derivatives. This percentage can have different meanings depending on the royalty policy being used - ie. it can be a relative percentage, an absolute percentage, an adjusted percentage according to specific rules, etc. 1. 10% between IP1 and IP2 2. 50% between IP2 and IP3 ### External Royalty Policies receive royalty tokens from their users' IPs Following the example, when each remix is made and during the `onLinkToParents` function call in [RoyaltyModule.sol](https://github.com/storyprotocol/protocol-core-v1/blob/main/contracts/modules/royalty/RoyaltyModule.sol), the function `getPolicyRtsRequiredToLink` is called on the "Policy X" address. ```solidity IExternalRoyaltyPolic.sol theme={null} /// @notice Returns the amount of royalty tokens required to link a child to a given IP asset /// @param ipId The ipId of the IP asset /// @param licensePercent The percentage of the license /// @return The amount of royalty tokens required to link a child to a given IP asset function getPolicyRtsRequiredToLink(address ipId, uint32 licensePercent) external view returns (uint32); ``` It should return the % of derivative's royalty tokens that the royalty policy demands for the link to happen. That share of royalty tokens are sent to the "Policy X" contract. In the example case: * "Policy X" receives 3% of RT2 token supply that it can then redistributed to its userbase. IP1 owner wanted 10%, however - let's assume for the sake of the example - that due to the specific use case of "Policy X" and its custom logic, the IP2 owner is granted a special status in the platform in which it it has a 70% discount on the % share it has to give parent IPs due to having a very large distribution network to promote IPs. Therefore, instead of having to give 10% as the license percentage indicated it only gives 3%. * "Policy X" receives 50% of RT3 token supply that it can then redistributed to its userbase. Royalty Token Distribution ### External Royalty Policies redistribute value back to their users according to custom rules There are two ways in which an External Royalty Policy can redistribute value back to its users: 1. Send Royalty Tokens directly to its users 2. Keep the Royalty Tokens in the External Royalty Policy contract and have users claim Revenue Tokens through the said contract Let's explore both in the context of "Policy X". Let's say that from the 50% of RT3 token supply "Policy X" received - 40% are kept in the "Policy X" contract and 10% are sent to an ancestor royalty vault (IP1). Royalty Token Redistribution Now let's imagine there is a 1M payment made to IP3 - an example of how the flow would be: Payment Flow Example From the 1M WIP inflow to IP3 Royalty Vault: * 500k WIP are claimed by the IP Account 3 which had 50% of RT3 token supply * 100k WIP are claimed by the IP1 Royalty Vault which has 10% of RT3 token supply via `claimByTokenBatchAsSelf` function * 400k WIP are claimed by "Policy X" which has 40 of RT3 token supply. This amount is further split by "Policy X" custom contract according to its specific rules - which define y% and z% - to its users. # IP Royalty Vault Source: https://docs.story.foundation/concepts/royalty-module/ip-royalty-vault Learn about IP Royalty Vaults and how they manage revenue for IP Assets An IP Royalty Vault is where all monetary inflows related to an IP Asset are stored. This revenue is then claimed with **Royalty Tokens**. IP Royalty Vault View the smart contract for the IP Royalty Vault. ## Royalty Tokens When an IP Asset receives revenue, it is deposited into its IP Royalty Vault. In order to claim revenue from this vault, you must have the associated **Royalty Tokens**. Every vault has 100 Royalty Tokens associated with it. If an address owns these tokens, it is entitled to that % (% of the total supply of Royalty Tokens owned) of any future revenue in the IP Royalty Vault. The IP Royalty Vault contract is also the ERC-20 contract for the **Royalty Tokens** of each IP Asset. This means the address of the IP Royalty Vault for an IP Asset is also the ERC-20 token address of the Royalty Tokens. ## Whitelisted Payment Tokens An ERC-20 token must be whitelisted by our protocol in the [RoyaltyModule.sol contract](https://github.com/storyprotocol/protocol-core-v1/blob/e339f0671c9172a6699537285e32aa45d4c1b57b/contracts/modules/royalty/RoyaltyModule.sol#L50) to be used for making payments. Here are the whitelisted tokens: | Token | Contract Address | Explorer | Mint | | :----- | :------------------------------------------- | :--------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | | WIP | `0x1514000000000000000000000000000000000000` | [View here ↗️](https://aeneid.storyscan.io/address/0x1514000000000000000000000000000000000000) | N/A | | MERC20 | `0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E` | [View here ↗️](https://aeneid.storyscan.io/address/0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E) | [Mint ↗️](https://aeneid.storyscan.io/address/0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E?tab=write_contract#0x40c10f19) | | Token | Contract Address | Explorer | Mint | | :---- | :------------------------------------------- | :--------------------------------------------------------------------------------------------- | :--- | | WIP | `0x1514000000000000000000000000000000000000` | [View here ↗️](https://aeneid.storyscan.io/address/0x1514000000000000000000000000000000000000) | N/A | ## How to obtain Royalty Tokens? There are two ways that trigger the IP Royalty Vault deployment and make the initial Royalty Token distribution - whichever comes first: 1. A License Token is minted from an IP for the first time. 2. A derivative is registered under the IP. In each case, the associated [IP Account](/concepts/ip-asset/ip-account), which is the IP itself, receives 100% of the Royalty Tokens. Because Royalty Tokens are ERC-20, they can be transferred like any other token. Thus, the IP Account could send them to someone else, or even put them up for sale on the secondary market. Once they are transferred, the new owner now has the right to claim x% of the revenue from the royalty vault. ## Examples Let's look at some examples of royalty flow from the IP Royalty Vault to Royalty Token holders. ### Default Case By default, [when the royalty vault is deployed](/concepts/royalty-module/ip-royalty-vault#how-to-obtain-royalty-tokens%3F), the IP Account (which is the IP itself) receives 100% of the Royalty Tokens. Let's look at an example where the IP Account is the full owner of the Royalty Tokens, and a payment is received into the IP Royalty Vault. Royalty Tokens Default As you can see, each IP claims the full amount from its royalty vault because it has 100% of the royalty tokens. ### Multiple Royalty Token Destinations Now let's look at a case where the IP Asset 2 has distributed 5% of its Royalty Tokens to another address. Maybe this is because they sold them on the secondary market, or maybe it's because they gave them to a friend. Multiple Royalty Token Destinations As you can see, IP Asset 1 claims its full 10 \$WIP. But IP Asset 2 only claims 9.5 \$WIP. The other 0.5 \$WIP is claimed by the address that owns the 5% of the Royalty Tokens. ## Distributing Royalty Tokens One of the most common questions is: *"Why does royalty end up in the IP Account? Shouldn't it end up in the IP owner's wallet?"* While the IP Account is the initial owner of 100% of the Royalty Tokens, remember that as the IP owner, you can transfer them to whoever. An easy solution is to transfer 100% of the Royalty Tokens to the IP owner's wallet, so that when revenue is received to the IP Royalty Vault, revenue can be claimed directly to the wallet. View a working code example of transferring Royalty Tokens to an external wallet (like the IP owner's wallet). Remember, Royalty Tokens are simple ERC-20s! Royalty Tokens Transferred # Liquid Absolute Percentage (LAP) Source: https://docs.story.foundation/concepts/royalty-module/liquid-absolute-percentage Learn how the Liquid Absolute Percentage royalty policy works. The Liquid Absolute Percentage (LAP) defines that each parent IP Asset can choose a minimum royalty percentage that **all** of its downstream IP Assets in a derivative chain will share from their monetary gains as defined in the license agreement. View the smart contract for the LAP Royalty Policy. ## Calculated Royalty Stack In the image below, IPA 1 and IPA 2 - due to being ancestors of IPA 3 - have a % economic right over revenue made by IPA 3. Key notes to understand the derivative chain below: * **License Royalty %**: this percentage is selected by the user and it means the percentage that the user wants - according to LAP rules - in return for allowing other users to remix its IPA. * **Royalty Stack**: the total revenue % an IPA has to pay to all its ancestors. For LAP, it's the sum of parents royalty stack + sum of licenses percentages used to connect to each parent * Royalty Stack IPA 2 = Royalty Stack IPA 1 + License Royalty % between IPAs 1 and 2 = 0% + 5% = 5% * Royalty Stack IPA 3 = Royalty Stack IPA 2 + License Royalty % between IPAs 2 and 3 = 5% + 10% = 15% The "License Royalty %" in this diagram corresponds to the same value as the `commercialRevShare` on the [PIL terms](/concepts/programmable-ip-license/pil-terms). royalty vault for LAP ## Royalty Module Split Let's show an example where a payment is made to IPA 3. In the below diagram, you can see that initial payment is forwarded to the Royalty Module. The Royalty Module then splits the payment based on the **Royalty Stack** determined by the LAP policy: * 15 \$WIP is sent to the LAP policy contract because of the 15% LAP royalty stack * 85 \$WIP is sent to the IPA 3 vault royalty module split ## Royalty Policy LAP Distribution After this initial payment is complete, IPA 1 and 2 can claim their revenue from the LAP policy contract. The LAP policy contract will then distribute the revenue to the parents based on the negotiatedrevenue share percentages: * IPA2 gets 10% absolute percentage of IPA3's revenue (10 \$WIP) * IPA1 gets 5% absolute percentage of IPA3's revenue (5 \$WIP) royalty policy distribution # Liquid Relative Percentage (LRP) Source: https://docs.story.foundation/concepts/royalty-module/liquid-relative-percentage Learn how the Liquid Relative Percentage royalty policy works. The Liquid Relative Percentage (LRP) royalty policy defines that each parent IP Asset can choose a minimum royalty percentage that **only the direct derivative IP Assets in a derivative chain** will share from their monetary gains as defined in the license agreement. View the smart contract for the LRP Royalty Policy. ## Calculated Royalty Stack In the image below, IPA 1 and IPA 2 - due to being ancestors of IPA 3 - have a % economic right over revenue made by IPA 3. Key notes to understand the derivative chain below: * **License Royalty %**: this percentage is selected by the user and it means the percentage that the user wants - according to LRP rules - in return for allowing other users to remix its IPA. * **Royalty Stack**: the total revenue % an IPA has to pay to all its parents. For LRP, it's the sum of license percentages used to connect to each parent * Royalty Stack IPA 2 = License Royalty % between IPAs 1 and 2 = 5% * Royalty Stack IPA 3 = License Royalty % between IPAs 2 and 3 = 10% The "License Royalty %" in this diagram corresponds to the same value as the `commercialRevShare` on the [PIL terms](/concepts/programmable-ip-license/pil-terms). royalty vault for LRP ## Royalty Module Split Let's show an example where a payment is made to IPA 3. In the below diagram, you can see that initial payment is forwarded to the Royalty Module. The Royalty Module then splits the payment based on the **Royalty Stack** determined by the LRP policy: * 10 \$WIP is sent to the LRP policy contract because of the 10% LRP royalty stack * 90 \$WIP is sent to the IPA 3 vault royalty module split ## Royalty Policy LRP Distribution After this initial payment is complete, IPA 1 and 2 can claim their revenue from the LRP policy contract. The LRP policy contract will then distribute the revenue to the parents based on the negotiated revenue share percentages: * IPA2 gets 10% of IPA3's revenue (10 \$WIP) * IPA1 gets 5% of IPA2's revenue (0.5 \$WIP) * IPA2 is left with 9.5 \$WIP royalty policy distribution # 💸 Royalty Module Source: https://docs.story.foundation/concepts/royalty-module/overview Learn how to pay, route, and claim royalties on Story. Story's Royalty Module enables automated revenue sharing between [IP Assets (IPAs)](/concepts/ip-asset/overview) based on their derivative relationships and license terms. This document explains how revenue flows through the protocol and how IP owners are paid and can claim their share. A working code example using the TypeScript SDK that shows how to pay and claim royalties. View the smart contract for the Royalty Module. # Conceptual Overview In order to learn the Royalty Module, let's look at a full example just so you can see what it looks like. Then we will walk through each part step-by-step to get a comprehensive understanding. Royalty Module Example Overview ## The Ancestory Graph IP Assets are connected by their derivative relationships. These relationships are bound via [License Terms](/concepts/licensing-module/license-terms), which specify things like an initial minting fee or a royalty percentage that must be paid every time revenue is generated. In this example, let's say we have 3 IP Assets with the following configurations: | IP Asset | Relationship | Commercial Revenue Share | | ----------------- | -------------------- | --------------------------------- | | IP Asset 1 (IPA1) | Original IP Asset | Requires 5% from all derivatives | | IP Asset 2 (IPA2) | A derivative of IPA1 | Requires 10% from all derivatives | | IP Asset 3 (IPA3) | A derivative of IPA2 | | ### Royalty Stack Each IP Asset has what's called a **Royalty Stack**. This is a cumulative percentage of revenue that must be paid to ancestors based on the license terms between the IP Assets. Royalty Module Example Ancestry Graph For example, if IPA3 makes 100 \$IP, it will eventually only take in 85 \$IP because 15 \$IP will be paid to its ancestors. This is because the royalty stack is 15% (5% from IPA1 and 10% from IPA2). ## IP Royalty Vault Upon creation, every IP Asset automatically gets a **Royalty Vault**. This is a vault - separate from the actual IP itself but bound to it - that receives all incoming revenue generated by the IP Asset. ### Royalty Tokens This royalty vault has 100 **Royalty Tokens** associated with it, where each token represents the right to 1% of the total revenue generated by the IP Asset and thus deposited to the Royalty Vault. When an IP Asset is created and a Royalty Vault is deployed, it automatically sends the 100 Royalty Tokens to the IP Asset itself. Royalty Module Example Royalty Vault These Royalty Tokens can be sent out to other wallets by the IP owner. Then, as stated above, whoever owns them has the right to claim their % due share of the revenue that gets accepted into the Royalty Vault. This unlocks some really cool DeFi opportunities. For example, selling Royalty Tokens on the secondary market where people can buy them and then claim their share of the revenue when it gets deposited into the Royalty Vault. ## Whitelisted Payment Tokens For a currency to be used in Story's Royalty Module, it must be whitelisted by our protocol in the [RoyaltyModule.sol contract](https://github.com/storyprotocol/protocol-core-v1/blob/e339f0671c9172a6699537285e32aa45d4c1b57b/contracts/modules/royalty/RoyaltyModule.sol#L50). Here are the whitelisted tokens: | Token | Contract Address | Explorer | Mint | | :----- | :------------------------------------------- | :--------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | | WIP | `0x1514000000000000000000000000000000000000` | [View here ↗️](https://aeneid.storyscan.io/address/0x1514000000000000000000000000000000000000) | N/A | | MERC20 | `0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E` | [View here ↗️](https://aeneid.storyscan.io/address/0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E) | [Mint ↗️](https://aeneid.storyscan.io/address/0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E?tab=write_contract#0x40c10f19) | | Token | Contract Address | Explorer | Mint | | :---- | :------------------------------------------- | :--------------------------------------------------------------------------------------------- | :--- | | WIP | `0x1514000000000000000000000000000000000000` | [View here ↗️](https://aeneid.storyscan.io/address/0x1514000000000000000000000000000000000000) | N/A | ## Initiating a Payment There are two common scenarios when revenue flow would be initiated: | Payment Type | When It Happens | Potential Functions Called | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | | Minting a License | When someone mints a license to use your IP, or registers a derivative of an IP Asset (which requires having a license) | `mintLicenseTokens`, `registerDerivative` | | Paying Revenue Directly | When someone directly sends revenue to an IP Asset either as a tip or to forward due revenue to the chain. | `payRoyaltyOnBehalf` | Let's see what happens when IP Asset 3 earns money using the [payRoyaltyOnBehalf](/sdk-reference/royalty#payroyaltyonbehalf) function. In our example, let's assume that IP Asset 3 is a video game character that is earning 100 \$IP. This payment is made via `payRoyaltyOnBehalf`, which will initiate the payment process as seen below. Pay Royalty On Behalf Next, the Royalty Module will split the payment based on the **royalty stack** (15%) and distribute the funds to both the IP Asset and the Royalty Policy contract which governs the license terms. Royalty policies will be explained in a different section. Royalty Module Split A tutorial on how to pay an IP revenue using the TypeScript SDK. A tutorial on how to pay an IP revenue using the smart contracts. ## Claiming Revenue The above walked through what happens when a payment is made. However, in order to actually get the revenue, the revenue must be claimed to whoever holds the Royalty Tokens. Imagine an IP graph with thousands of ancestors. If we were to pay, distribute, and claim all at once, the transaction would be too large to handle. By separating the payment and claiming processes, we can break down the process into smaller chunks that are more gas efficient. Claiming revenue can be done with the [claimAllRevenue](/sdk-reference/royalty#claimallrevenue) function. Let's see how it works. Claiming revenue is completely permissionless. Anyone can claim revenue on behalf of any IP Asset. Since IP Asset 3 was paid directly, the revenue automatically went to it's Royalty Vault. However because IP Asset 1 and 2 earned their revenue from a derivative relationship, the revenue must be transferred from the royalty policy to their Royalty Vaults. Royalty Policy Distribution In the last step, revenue will be transferred from the Royalty Vault to whoever owns the Royalty Tokens for each associated IP Asset. This example is simple, because each IP Asset is holding all 100 of its Royalty Tokens in the IP itself. Royalty Policy Distribution A tutorial on how to claim revenue using the TypeScript SDK. A tutorial on how to claim revenue using the smart contracts. # Batch Function Calls Source: https://docs.story.foundation/concepts/spg/batch-spg-function-calls Learn how to batch multiple operations into a single transaction for efficiency ## Background Prior to this point, registering multiple IPs or performing other operations such as minting, attaching licensing terms, and registering derivatives requires separate transactions for each operation. This can be inefficient and costly. To streamline the process, you can batch multiple transactions into a single one. Two solutions are now available for this: 1. **Batch SPG function calls:** Use [SPG's built-in `multicall` function](#1-batch-spg-function-calls-via-built-in-multicall-function). 2. **Batch function calls beyond SPG:** Use the [Multicall3 Contract](#2-batch-function-calls-via-multicall3-contract). *** ## 1. Batch SPG Function Calls via Built-in `multicall` Function SPG includes a `multicall` function that allows you to combine multiple read or write operations into a single transaction. ### Function Definition The `multicall` function accepts an array of encoded call data and returns an array of encoded results corresponding to each function call: ```solidity Solidity theme={null} /// @dev Executes a batch of function calls on this contract. function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results); ``` ### Example Usage Suppose you want to mint multiple NFTs, register them as IPs, and link them as derivatives to some parent IPs. To accomplish this, you can use SPG's `multicall` function to batch the calls to the `mintAndRegisterIpAndMakeDerivative` function. Here's how you might do it: ```solidity Solidity theme={null} // an SPG workflow contract: https://github.com/storyprotocol/protocol-periphery-v1/blob/main/contracts/workflows/DerivativeWorkflows.sol contract DerivativeWorkflows { ... function mintAndRegisterIpAndMakeDerivative( address nftContract, MakeDerivative calldata derivData, IPMetadata calldata ipMetadata, address recipient ) external returns (address ipId, uint256 tokenId) { .... } ... } ``` To batch call `mintAndRegisterIpAndMakeDerivative` using the `multicall` function: ```javascript JavaScript theme={null} // batch mint, register, and make derivatives for multiple IPs await DerivativeWorkflows.multicall([ DerivativeWorkflows.contract.methods.mintAndRegisterIpAndMakeDerivative( nftContract1, derivData1, recipient1, ipMetadata1, ).encodeABI(), DerivativeWorkflows.contract.methods.mintAndRegisterIpAndMakeDerivative( nftContract2, derivData2, recipient2, ipMetadata2, ).encodeABI(), DerivativeWorkflows.contract.methods.mintAndRegisterIpAndMakeDerivative( nftContract3, derivData3, recipient3, ipMetadata3, ).encodeABI(), ... // Add more calls as needed ]); ``` *** ## 2. Batch Function Calls via Multicall3 Contract The Multicall3 contract is not fully compatible with SPG functions that involve SPGNFT minting due to access control and context changes during Multicall execution. For such operations, use [SPG's built-in multicall function.](#1-batch-spg-function-calls-via-built-in-multicall-function) The Multicall3 contract allows you to execute multiple calls within a single transaction and aggregate the results. The [`viem` library](https://viem.sh/docs/contract/multicall#multicall) provides native support for Multicall3. ### Story Aeneid Testnet Multicall3 Deployment Info (Same address across all EVM chains) ```json theme={null} { "contractName": "Multicall3", "chainId": 1516, "contractAddress": "0xcA11bde05977b3631167028862bE2a173976CA11", "url": "https://aeneid.storyscan.io/address/0xcA11bde05977b3631167028862bE2a173976CA11" } ``` ### Main Functions To batch multiple function calls, you can use the following functions: 1. **`aggregate3`**: Batches calls using the `Call3` struct. 2. **`aggregate3Value`**: Similar to `aggregate3`, but also allows attaching a value to each call. ```solidity Solidity theme={null} /// @notice Aggregate calls, ensuring each returns success if required. /// @param calls An array of Call3 structs. /// @return returnData An array of Result structs. function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData); /// @notice Aggregate calls with an attached msg value. /// @param calls An array of Call3Value structs. /// @return returnData An array of Result structs. function aggregate3Value(Call3Value[] calldata calls) external payable returns (Result[] memory returnData); ``` ### Struct Definitions * **Call3**: Used in `aggregate3`. * **Call3Value**: Used in `aggregate3Value`. ```solidity Solidity theme={null} struct Call3 { address target; // Target contract to call. bool allowFailure; // If false, the multicall will revert if this call fails. bytes callData; // Data to call on the target contract. } struct Call3Value { address target; bool allowFailure; uint256 value; // Value (in wei) to send with the call. bytes callData; // Data to call on the target contract. } ``` ### Return Type * **Result**: Struct returned by both `aggregate3` and `aggregate3Value`. ```solidity Solidity theme={null} struct Result { bool success; // Whether the function call succeeded. bytes returnData; // Data returned from the function call. } ``` For detailed examples in Solidity, TypeScript, and Python, see the [Multicall3 repository](https://github.com/mds1/multicall/tree/main/examples). ### Limitations For a list of limitations when using Multicall3, refer to the [Multicall3 README](https://github.com/mds1/multicall/blob/main/README.md#batch-contract-writes). ### Additional Resources * [Multicall3 Documentation](https://github.com/mds1/multicall/blob/main/README.md) * [Multicall Documentation from Viem](https://viem.sh/docs/contract/multicall#multicall) ### Full Multicall3 Interface ```solidity Solidity theme={null} interface IMulticall3 { struct Call { address target; bytes callData; } struct Call3 { address target; bool allowFailure; bytes callData; } struct Call3Value { address target; bool allowFailure; uint256 value; bytes callData; } struct Result { bool success; bytes returnData; } function aggregate(Call[] calldata calls) external payable returns (uint256 blockNumber, bytes[] memory returnData); function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData); function aggregate3Value(Call3Value[] calldata calls) external payable returns (Result[] memory returnData); function blockAndAggregate(Call[] calldata calls) external payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData); function getBasefee() external view returns (uint256 basefee); function getBlockHash(uint256 blockNumber) external view returns (bytes32 blockHash); function getBlockNumber() external view returns (uint256 blockNumber); function getChainId() external view returns (uint256 chainId); function getCurrentBlockCoinbase() external view returns (address coinbase); function getCurrentBlockDifficulty() external view returns (uint256 difficulty); function getCurrentBlockGasLimit() external view returns (uint256 gaslimit); function getCurrentBlockTimestamp() external view returns (uint256 timestamp); function getEthBalance(address addr) external view returns (uint256 balance); function getLastBlockHash() external view returns (bytes32 blockHash); function tryAggregate(bool requireSuccess, Call[] calldata calls) external payable returns (Result[] memory returnData); function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) external payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData); } ``` # 📦 SPG (Periphery) Source: https://docs.story.foundation/concepts/spg/overview Learn about the Story Protocol Gateway that simplifies interactions with the protocol The Story Protocol Gateway (SPG) is a group of periphery/utility smart contracts, deployed on our protocol that **allows you to combine independent operations** - like registering an [🧩 IP Asset](/concepts/ip-asset/overview) and attaching License Terms to that IP Asset - **into one transaction to make your life easier**. This was primarily developed to make our [SDK](/sdk-reference) easier to use. For example, this `mintAndRegisterIpAndAttachPILTerms` is one of the functions in the SPG (more specifically in the `LicenseAttachmentWorkflows.sol`) that allows you to mint an NFT, register it as an IP Asset, and attach License Terms to it all in one call: ```solidity LicenseAttachmentWorkflows.sol theme={null} function mintAndRegisterIpAndAttachPILTerms( address spgNftContract, address recipient, WorkflowStructs.IPMetadata calldata ipMetadata, WorkflowStructs.LicenseTermsData[] calldata licenseTermsData, bool allowDuplicates ) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds) ``` ## All Supported Workflows As mentioned above, there are many different functions we have created for you that combine multiple functions into one. We have categorized them into different groups. These groups are called "workflows". Click here to view all of the supported workflows. Click here to view the workflow smart contracts. ## Batching Calls Although the SPG contains certain functions like `mintAndRegisterIpAndAttachPILTerms`, `registerIpAndAttachPILTerms`, and a bunch more, it would be tedious for us to continually update the contract to account for every single combination of possible interactions with an IP Asset. Instead, we have allowed for a "Multicall" mechanism where you can batch transactions how you like. For more info, see [Batch Function Calls](/concepts/spg/batch-spg-function-calls). # Story Attestation Service Source: https://docs.story.foundation/concepts/story-attestation-service A multi-layered decentralized approach to validating intellectual property. You can think of the Story Attestation Service (SAS) as a bunch of independent service providers each proving the validity of an IP in their own way. So that each IP has a set of "badges" on it displaying the results. It's then up to the ecosystem/market to determine which providers they trust or want to believe. This becomes a decentralized "validator"-like approach to IP validity, where if an IP Asset has lots of providers saying it is valid, then it's probably valid. Story employs a multi-layered decentralized approach to validating intellectual property, grounded in two foundational components: 1. The Story Attestation Service (SAS): leverages a network of specialized service providers — each detecting copyright violations across different mediums (images, audio, etc) — to provide transparent, publicly accessible signals on the legitimacy of an [🧩 IP Asset](/concepts/ip-asset). Applications that facilitate IP registration (e.g. original content) may also attest to the provenance of an IP asset (called "apptestations") in the future. 2. The [❌ Dispute Module](/concepts/dispute-module): offers a flexible framework for resolving conflicts, tapping both on-chain and off-chain processes to accommodate the nuanced nature of IP disputes. This blend of detection methods and dispute resolution creates a robust ecosystem that allows IP to be registered without introducing undue friction, while letting the market, and individual ecosystem apps, determine how much weight to give each attestation provider. These layers make up the **IP Validation Service (IPVS)** - a fully decentralized marketplace of trust. The existing system of detection providers will continue to expand into a broader ecosystem of signal contributors, each able to offer specialized, verifiable assessments of IP authenticity. Through incentivized participation, IPVS fosters a self-sustaining market where different validators collaborate to deliver specialized signals. So rather than preventing duplicates, which would cause far more potentially disruptive front running risk, the signals and attestations allow the original IPs surface above the rest. ## "When does the SAS scan my IP?" It's important to note that the Story Attestation Service only runs IP infringement checks on **commercial IP**. That is, IP Assets who have at least one [License Terms](/concepts/licensing-module/license-terms) where `commercialUse = true`. If your IP is non-commercial, then this section doesn't apply to you. When [registering your IP on Story](/developers/tutorials/how-to-register-ip-on-story), you pass in IP-specific metadata that implements the [📝 IPA Metadata Standard](/concepts/ip-asset/ipa-metadata-standard). In this standard, you'll see 3 fields: | Property Name | Type | Description | | :------------ | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mediaUrl` | `string` | Used for infringement checking, points to the actual media (ex. image or audio) | | `mediaHash` | `string` | Hashed string of the media using SHA-256 hashing algorithm. See [here](/concepts/ip-asset/ipa-metadata-standard#hashing-content) for how that is done. | | `mediaType` | `string` | Type of media (audio, video, image), based on [mimeType](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types). See the allowed media types [here](/concepts/ip-asset/ipa-metadata-standard#media-types). | These are used for the commercial infringement check. Whatever media you pass in through `mediaUrl` will be checked by our infringement detection providers and flagged if infringement is detected. If you do not pass in these `media.*` fields, then an infringement detection will not be performed and your IP will not be proven valid. ### Current Limitations * You must set the `media.*` fields before attaching commercial terms (`commercialUse = true`), otherwise no check will be performed. * Attestations will only show up on the IP Portal (our "GitHub for IP" platform coming soon). We are working on publishing attestations to public record so anyone can access the results (**COMING SOON!**). * Only media that is **existing on the internet** will be detected. If someone registers new IP on Story, it will simply return validated because our providers don't have data on it. ## Current Providers Yakoa uses AI and machine learning to scan multiple blockchains, analyzing on-chain data to detect direct copies, stylistic forgeries, and unauthorized replications of digital assets. It compares new assets against a database of known IP, flagging potential violations in real time and providing detailed audit logs for enforcement. Pex.com is a digital platform that leverages advanced content recognition and analytics to help creators and rights holders track, manage, and monetize their visual and audio media online. It monitors how content is used across the web, making it easier for users to discover licensing opportunities and protect their intellectual property. ## Becoming an Attestation Provider The Story Attestation Service is undergoing active development. If you run any form of IP validation (infringement, identity, origin, etc), then you can become an attestation provider. To do so, please fill out this [form](https://docs.google.com/forms/d/10n3AnWoiLsxpaY17kJlxRazysDe8aOWJgirRnfkFRAk/edit). # Demo Source: https://docs.story.foundation/demo