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 license tokens derived from a set of parent IPAs from which you wish to remix. (In some special cases, you can remix without license tokens!)

For a detailed understanding of how to generate an IPA license token, please see the section Mint a License Token for an IPA. A license token represents a set of license terms (e.g. PIL) bound to an existing IPA that may be used to create a derivative IPA that would inherit said license terms.

Now that we've covered how derivative IPAs work, let's go over how you would remix your IP using the Asset Registry. (This section assumes that you have SimpleNFT.sol and the development environment setup from previous sections.)

Remixing via the IP Asset Registry

Because the IPA registry is fully permissionless, you can also remix using licenses directly from the IPA Registry.

Let's see how we can perform a remix using the IP Asset Registry directly. We use the same contract as the previous section (Mint a License Token for an IPA):

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

import { IPAssetRegistry } from "@storyprotocol/core/registries/IPAssetRegistry.sol";
import { LicensingModule } from "@storyprotocol/core/modules/licensing/LicensingModule.sol";
import { PILicenseTemplate } from "@storyprotocol/core/modules/licensing/PILicenseTemplate.sol";

import { SimpleNFT } from "./SimpleNFT.sol";

/// @notice Mint a License Token from Programmable IP License Terms attached to an IP Account.
contract IPALicenseToken {
    IPAssetRegistry public immutable IP_ASSET_REGISTRY;
    LicensingModule public immutable LICENSING_MODULE;
    PILicenseTemplate public immutable PIL_TEMPLATE;
    SimpleNFT public immutable SIMPLE_NFT;

    constructor(address ipAssetRegistry, address licensingModule, address pilTemplate) {
        IP_ASSET_REGISTRY = IPAssetRegistry(ipAssetRegistry);
        LICENSING_MODULE = LicensingModule(licensingModule);
        PIL_TEMPLATE = PILicenseTemplate(pilTemplate);
        // Create a new Simple NFT collection
        SIMPLE_NFT = new SimpleNFT("Simple IP NFT", "SIM");
    }

    function mintLicenseToken(
        uint256 ltAmount,
        address ltRecipient
    ) external returns (address ipId, uint256 tokenId, uint256 startLicenseTokenId) {
        // First, mint an NFT and register it as an IP Account.
        // Note that first we mint the NFT to this contract for ease of attaching license terms.
        // We will transfer the NFT to the msg.sender at last.
        tokenId = SIMPLE_NFT.mint(address(this));
        ipId = IP_ASSET_REGISTRY.register(block.chainid, address(SIMPLE_NFT), tokenId);

        // Then, attach a selection of license terms from the PILicenseTemplate, which is already registered.
        // Note that licenseTermsId = 2 is a Non-Commercial Social Remixing (NSCR) license.
        // Read more about NSCR: https://docs.storyprotocol.xyz/docs/pil-flavors#flavor-1-non-commercial-social-remixing
        LICENSING_MODULE.attachLicenseTerms(ipId, address(PIL_TEMPLATE), 2);

        // Then, mint a License Token from the attached license terms.
        // Note that the License Token is minted to the ltRecipient.
        startLicenseTokenId = LICENSING_MODULE.mintLicenseTokens({
            licensorIpId: ipId,
            licenseTemplate: address(PIL_TEMPLATE),
            licenseTermsId: 2,
            amount: ltAmount,
            receiver: ltRecipient,
            royaltyContext: "" // for PIL, royaltyContext is empty string
        });

        // Finally, transfer the NFT to the msg.sender.
        SIMPLE_NFT.transferFrom(address(this), msg.sender, tokenId);
    }
}

The difference is in the test, where we utilize the minted PIL NSCR license tokens to create a derivative IP.

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

import { Test } from "forge-std/Test.sol";
import { LicenseToken } from "@storyprotocol/core/LicenseToken.sol";
import { LicensingModule } from "@storyprotocol/core/modules/licensing/LicensingModule.sol";
import { IPAssetRegistry } from "@storyprotocol/core/registries/IPAssetRegistry.sol";
import { LicenseRegistry } from "@storyprotocol/core/registries/LicenseRegistry.sol";

import { IPALicenseToken } from "../src/IPALicenseToken.sol";
import { SimpleNFT } from "../src/SimpleNFT.sol";

contract IPARemixTest is Test {
    address internal alice = address(0xa11ce);
    address internal bob = address(0xb0b);

    // Protocol Core v1 addresses
    // (see https://docs.storyprotocol.xyz/docs/deployed-smart-contracts)
    address internal ipAssetRegistryAddr = 0xd43fE0d865cb5C26b1351d3eAf2E3064BE3276F6;
    address internal licensingModuleAddr = 0xe89b0EaA8a0949738efA80bB531a165FB3456CBe;
    address internal licenseRegistryAddr = 0x4f4b1bf7135C7ff1462826CCA81B048Ed19562ed;
    address internal licenseTokenAddr = 0x1333c78A821c9a576209B01a16dDCEF881cAb6f2;
    address internal pilTemplateAddr = 0x260B6CB6284c89dbE660c0004233f7bB99B5edE7;

    IPAssetRegistry public ipAssetRegistry;
    LicensingModule public licensingModule;
    LicenseRegistry public licenseRegistry;
    LicenseToken public licenseToken;

    IPALicenseToken public ipaLicenseToken;
    SimpleNFT public simpleNft;

    function setUp() public {
        ipAssetRegistry = IPAssetRegistry(ipAssetRegistryAddr);
        licensingModule = LicensingModule(licensingModuleAddr);
        licenseRegistry = LicenseRegistry(licenseRegistryAddr);
        licenseToken = LicenseToken(licenseTokenAddr);
        ipaLicenseToken = new IPALicenseToken(ipAssetRegistryAddr, licensingModuleAddr, pilTemplateAddr);
        simpleNft = SimpleNFT(ipaLicenseToken.SIMPLE_NFT());

        vm.label(address(ipAssetRegistryAddr), "IPAssetRegistry");
        vm.label(address(licensingModuleAddr), "LicensingModule");
        vm.label(address(licenseRegistryAddr), "LicenseRegistry");
        vm.label(address(licenseTokenAddr), "LicenseToken");
        vm.label(address(pilTemplateAddr), "PILicenseTemplate");
        vm.label(address(simpleNft), "SimpleNFT");
        vm.label(address(0x000000006551c19487814612e58FE06813775758), "ERC6551Registry");
    }

    function test_remixIp() public {
        //
        // Alice mints License Tokens for Bob.
        //

        uint256 expectedTokenId = simpleNft.nextTokenId();
        address expectedIpId = ipAssetRegistry.ipId(block.chainid, address(simpleNft), expectedTokenId);

        vm.prank(alice);
        (address parentIpId, uint256 tokenId, uint256 startLicenseTokenId) = ipaLicenseToken.mintLicenseToken({
            ltAmount: 2,
            ltRecipient: bob
        });

        assertEq(parentIpId, expectedIpId);
        assertEq(tokenId, expectedTokenId);
        assertEq(simpleNft.ownerOf(tokenId), alice);

        assertEq(licenseToken.ownerOf(startLicenseTokenId), bob);
        assertEq(licenseToken.ownerOf(startLicenseTokenId + 1), bob);

        //
        // Bob uses the minted License Token from Alice to register a derivative IP.
        //

      	vm.prank(address(ipaLicenseToken)); // need to prank to mint simpleNft
        tokenId = simpleNft.mint(address(bob));
        address childIpId = ipAssetRegistry.register(block.chainid, address(simpleNft), tokenId);

        uint256[] memory licenseTokenIds = new uint256[](1);
        licenseTokenIds[0] = startLicenseTokenId;

        vm.prank(bob);
        licensingModule.registerDerivativeWithLicenseTokens({
            childIpId: childIpId,
            licenseTokenIds: licenseTokenIds,
            royaltyContext: "" // empty for PIL
        });

        assertTrue(licenseRegistry.hasDerivativeIps(parentIpId));
        assertTrue(licenseRegistry.isParentIp(parentIpId, childIpId));
        assertTrue(licenseRegistry.isDerivativeIp(childIpId));
        assertEq(licenseRegistry.getDerivativeIpCount(parentIpId), 1);
        assertEq(licenseRegistry.getParentIpCount(childIpId), 1);
        assertEq(licenseRegistry.getParentIp({ childIpId: childIpId, index: 0 }), parentIpId);
        assertEq(licenseRegistry.getDerivativeIp({ parentIpId: parentIpId, index: 0 }), childIpId);
    }
}