Once a License Token has been minted from an IP Asset, the owner of that token (an ERC-721 NFT) can burn it to register their own IP Asset as a derivative of the IP Asset associated with the License Token.
Let’s create a test file under test/4_IPARemix.t.sol to see it work and verify the results:
Contract Addresses
We have filled in the addresses from the Story contracts for you. However you can also find the addresses for them here: Deployed Smart Contracts
test/4_IPARemix.t.sol
Copy
Ask AI
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.26;import { Test } from "forge-std/Test.sol";// for testing purposes onlyimport { MockIPGraph } from "@storyprotocol/test/mocks/MockIPGraph.sol";import { IIPAssetRegistry } from "@storyprotocol/core/interfaces/registries/IIPAssetRegistry.sol";import { ILicenseRegistry } from "@storyprotocol/core/interfaces/registries/ILicenseRegistry.sol";import { IPILicenseTemplate } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol";import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol";import { PILFlavors } from "@storyprotocol/core/lib/PILFlavors.sol";import { PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol";import { SimpleNFT } from "../src/mocks/SimpleNFT.sol";// Run this test:// forge test --fork-url https://aeneid.storyrpc.io/ --match-path test/4_IPARemix.t.solcontract IPARemixTest is Test { address internal alice = address(0xa11ce); address internal bob = address(0xb0b); // For addresses, see https://docs.story.foundation/developers/deployed-smart-contracts // Protocol Core - IPAssetRegistry IIPAssetRegistry internal IP_ASSET_REGISTRY = IIPAssetRegistry(0x77319B4031e6eF1250907aa00018B8B1c67a244b); // Protocol Core - LicenseRegistry ILicenseRegistry internal LICENSE_REGISTRY = ILicenseRegistry(0x529a750E02d8E2f15649c13D69a465286a780e24); // Protocol Core - LicensingModule ILicensingModule internal LICENSING_MODULE = ILicensingModule(0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f); // Protocol Core - PILicenseTemplate IPILicenseTemplate internal PIL_TEMPLATE = IPILicenseTemplate(0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316); // Protocol Core - RoyaltyPolicyLAP address internal ROYALTY_POLICY_LAP = 0xBe54FB168b3c982b7AaE60dB6CF75Bd8447b390E; // Revenue Token - MERC20 address internal MERC20 = 0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E; SimpleNFT public SIMPLE_NFT; uint256 public tokenId; address public ipId; uint256 public licenseTermsId; uint256 public startLicenseTokenId; function setUp() public { // this is only for testing purposes // due to our IPGraph precompile not being // deployed on the fork vm.etch(address(0x0101), address(new MockIPGraph()).code); SIMPLE_NFT = new SimpleNFT("Simple IP NFT", "SIM"); tokenId = SIMPLE_NFT.mint(alice); ipId = IP_ASSET_REGISTRY.register(block.chainid, address(SIMPLE_NFT), tokenId); licenseTermsId = PIL_TEMPLATE.registerLicenseTerms( PILFlavors.commercialRemix({ mintingFee: 0, commercialRevShare: 10 * 10 ** 6, // 10% royaltyPolicy: ROYALTY_POLICY_LAP, currencyToken: MERC20 }) ); vm.prank(alice); LICENSING_MODULE.attachLicenseTerms(ipId, address(PIL_TEMPLATE), licenseTermsId); startLicenseTokenId = LICENSING_MODULE.mintLicenseTokens({ licensorIpId: ipId, licenseTemplate: address(PIL_TEMPLATE), licenseTermsId: licenseTermsId, amount: 2, receiver: bob, royaltyContext: "", // for PIL, royaltyContext is empty string maxMintingFee: 0, maxRevenueShare: 0 }); } /// @notice Mints an NFT to be registered as IP, and then /// linked as a derivative of alice's asset using the /// minted license token. function test_registerDerivativeWithLicenseTokens() public { // First we mint an NFT and register it as an IP Asset, // owned by Bob. uint256 childTokenId = SIMPLE_NFT.mint(bob); address childIpId = IP_ASSET_REGISTRY.register(block.chainid, address(SIMPLE_NFT), childTokenId); uint256[] memory licenseTokenIds = new uint256[](1); licenseTokenIds[0] = startLicenseTokenId; // Bob uses the License Token he has from Alice's IP // to register his IP as a derivative of Alice's IP. vm.prank(bob); LICENSING_MODULE.registerDerivativeWithLicenseTokens({ childIpId: childIpId, licenseTokenIds: licenseTokenIds, royaltyContext: "", // empty for PIL maxRts: 0 }); assertTrue(LICENSE_REGISTRY.hasDerivativeIps(ipId)); assertTrue(LICENSE_REGISTRY.isParentIp(ipId, childIpId)); assertTrue(LICENSE_REGISTRY.isDerivativeIp(childIpId)); assertEq(LICENSE_REGISTRY.getParentIpCount(childIpId), 1); assertEq(LICENSE_REGISTRY.getDerivativeIpCount(ipId), 1); assertEq(LICENSE_REGISTRY.getParentIp({ childIpId: childIpId, index: 0 }), ipId); assertEq(LICENSE_REGISTRY.getDerivativeIp({ parentIpId: ipId, index: 0 }), childIpId); }}
Now that we have established parent-child IP relationships, we can begin to explore payments and automated revenue share based on the license terms. We’ll cover that in the upcoming pages.