Remix an IP Asset

IPA Remixing

So far, we have walked through how to register your NFT as an IPA - but what if you wanted to register your IP as a remix of an existing one? This concept of derivative IP creation is known as remixing, and unlike standard IP registration, remixing requires you to present and burn a set of compatible licenses derived from a set of parent IPAs that you wish to remix.

For a detailed understanding on how to generate an IPA license, please see the section Create an IPA license. A license represents a set of policies bound to an existing IPA that may be used to create a derivative IPA which would inherit said policies.

Now that we've covered how derivative IPAs work, let's go over how you would remix your IP using the Asset Registry.

Remixing via the IP Asset Registry

Because the IPA registry is fully permissionless, you can also remix using licenses directly from the IPA registry. The only caveat is that it add custom metadata for you, as you will have to do that as an extra step yourself.

Let's see how we can perform a remix using the IP Asset Registry directly:

pragma solidity ^0.8.23;

import { IPAssetRegistry } from "@story-protocol/protocol-core/contracts/registries/IPAssetRegistry.sol";
import { IPResolver } from "@story-protocol/protocol-core/contracts/resolvers/IPResolver.sol";
import { IP } from "@story-protocol/protocol-core/contracts/lib/IP.sol";

contract ExampleIPARemixRegistration {

  uint256 public constant MIN_ROYALTY = 10;
  bytes ROYALTY_CONTEXT;
  IPResolver public resolver;
  IPAssetRegistry public immutable REGISTRY;

  constructor(address registry, address resolverAddr) {
    REGISTRY = IPAssetRegistry(registry);
    resolver = IPResolver(resolverAddr);
  }

  function remix(
  	uint256[] calldata licenseIds,
  	address tokenContract,
     uint256 tokenId
	) public returns (address ipId) {
      bytes memory metadata = abi.encode(
        IP.MetadataV1({
          name: "name for your IP asset",
          hash:  bytes32("your IP asset content hash"),
          registrationDate: uint64(block.timestamp),
          registrant: msg.sender,
          uri: "https://yourip.xyz/metadata-regarding-its-ip"
        })
      );

      ipId = REGISTRY.register(
        licenseIds,
        ROYALTY_CONTEXT,
        block.chainid,
        tokenContract,
        tokenId,
        address(resolver),
        true,
        metadata
      );
  }
}

In this case, we have a function that takes in a set of licenses from which you would like to derive your IP asset from.

Registration Delegation

As explained in the section Register an NFT as an IPA, you may want to delegate remixing to a third-party operator, for which there are two options.

The first option is to create a module that gets approved by the protocol. For more information on this, please see the section Build a periphery contract.

The other option, which is specific to IPA registration and remixing, is to use the IP Asset Registry's built-in approval functionality. A user who wishes to delegate remixing to another user or contract can simply call setApprovalForAll on the registry for the approved operator, much like you do with existing ERC-721 contracts. Below, we show how this can be tested:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

import { Test } from "forge-std/Test.sol";

import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { IPAssetRegistry } from "@story-protocol/core/registries/IPAssetRegistry.sol";
import { IPResolver } from "@story-protocol/core/resolvers/IPResolver.sol";

contract MockERC721 is ERC721 {

    uint256 totalSupply = 0;

    constructor(string memory name, string memory symbol) ERC721(name, symbol) {}

    function mint() external returns (uint256 id) {
        id = totalSupply++;
        _mint(msg.sender, id);
    }
}

contract IPARegistrarTest is Test {

    address payable internal alice = payable(vm.addr(0xa1c3));
    address payable internal bob = payable(vm.addr(0xb0b));

    address public constant IPA_REGISTRY_ADDR = address(0x7567ea73697De50591EEc317Fe2b924252c41608);
    address public constant IP_RESOLVER_ADDR = address(0xEF808885355B3c88648D39c9DB5A0c08D99C6B71);
		uint256 public constant TEST_LICENSE = 0;
  	bytes public ROYALTY_CONTEXT = "";
  
    MockERC721 public nft;

    function setUp() public {
        nft = new MockERC721("Story Mock NFT", "STORY");
    }

    function test_IPARegistrationApproval() public {
      uint256[] licenses  = new uint256[](1);
      licenses[0] = TEST_LICENSE;
      
      bytes memory metadata = abi.encode(
        IP.MetadataV1({
          name: "name for your IP asset",
          hash:  bytes32("your IP asset content hash"),
          registrationDate: uint64(block.timestamp),
          registrant: msg.sender,
          uri: "https://yourip.xyz/metadata-regarding-its-ip",
        })
      );
			
      // Get one NFT as Alice
      vm.prank(alice);
      uint256 tokenId = nft.mint(); 
      
      // If bob tries to register on behalf of Alice, it will fail.
      vm.prank(bob);
      vm.expectRevert();
      REGISTRY.register(block.chainid,
        licenses,
        ROYALTY_CONTEXT,
        tokenContract,
        tokenId,
        address(resolver),
        true,
        metadata
      );
       
      // If Alice however approves bob as an operator, it will succeed.
      vm.prank(alice);
      REGISTRY.setApprovalForAll(bob, true);
     	REGISTRY.register(
        licenses
        ROYALTY_CONTEXT,
        block.chainid,
        tokenContract,
        tokenId,
        address(resolver),
        true,
        metadata
      );
    }

}

Using these primitives around delegation can allow you to build powerful apps that simplifies the UX around user-managed NFT remixing. Again, it is important to note that for all other module interactions, delegation requires a very different authorization process, which you can read in Access Controller.