From c9ab046a41dc64d3d5be16614cef355b182cb488 Mon Sep 17 00:00:00 2001 From: Bonnie Date: Fri, 22 Aug 2025 18:02:31 +0800 Subject: [PATCH 1/6] Add mintAndRegisterIpAndMakeDerivativeWithLicenseTokens method from contract --- .../DerivativeWorkflows_client.py | 45 +++++++++++++++++++ .../scripts/config.json | 3 +- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/story_protocol_python_sdk/abi/DerivativeWorkflows/DerivativeWorkflows_client.py b/src/story_protocol_python_sdk/abi/DerivativeWorkflows/DerivativeWorkflows_client.py index dfd6d7c..536ee68 100644 --- a/src/story_protocol_python_sdk/abi/DerivativeWorkflows/DerivativeWorkflows_client.py +++ b/src/story_protocol_python_sdk/abi/DerivativeWorkflows/DerivativeWorkflows_client.py @@ -56,6 +56,51 @@ def build_mintAndRegisterIpAndMakeDerivative_transaction( spgNftContract, derivData, ipMetadata, recipient, allowDuplicates ).build_transaction(tx_params) + def mintAndRegisterIpAndMakeDerivativeWithLicenseTokens( + self, + spgNftContract, + licenseTokenIds, + royaltyContext, + maxRts, + ipMetadata, + recipient, + allowDuplicates, + ): + return ( + self.contract.functions.mintAndRegisterIpAndMakeDerivativeWithLicenseTokens( + spgNftContract, + licenseTokenIds, + royaltyContext, + maxRts, + ipMetadata, + recipient, + allowDuplicates, + ).transact() + ) + + def build_mintAndRegisterIpAndMakeDerivativeWithLicenseTokens_transaction( + self, + spgNftContract, + licenseTokenIds, + royaltyContext, + maxRts, + ipMetadata, + recipient, + allowDuplicates, + tx_params, + ): + return ( + self.contract.functions.mintAndRegisterIpAndMakeDerivativeWithLicenseTokens( + spgNftContract, + licenseTokenIds, + royaltyContext, + maxRts, + ipMetadata, + recipient, + allowDuplicates, + ).build_transaction(tx_params) + ) + def registerIpAndMakeDerivative( self, nftContract, tokenId, derivData, ipMetadata, sigMetadataAndRegister ): diff --git a/src/story_protocol_python_sdk/scripts/config.json b/src/story_protocol_python_sdk/scripts/config.json index 89604bb..58573fa 100644 --- a/src/story_protocol_python_sdk/scripts/config.json +++ b/src/story_protocol_python_sdk/scripts/config.json @@ -230,7 +230,8 @@ "contract_address": "0x9e2d496f72C547C2C535B167e06ED8729B374a4f", "functions": [ "registerIpAndMakeDerivative", - "mintAndRegisterIpAndMakeDerivative" + "mintAndRegisterIpAndMakeDerivative", + "mintAndRegisterIpAndMakeDerivativeWithLicenseTokens" ] } ] From 01686011ec2d252ffeba7061c9eb13876c0c988c Mon Sep 17 00:00:00 2001 From: Bonnie Date: Fri, 22 Aug 2025 18:03:10 +0800 Subject: [PATCH 2/6] Implement mint_and_register_ip_and_make_derivative_with_license_tokens method --- .../resources/IPAsset.py | 63 +++++++++++++++---- .../utils/validation.py | 11 ++++ 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/story_protocol_python_sdk/resources/IPAsset.py b/src/story_protocol_python_sdk/resources/IPAsset.py index 8615541..b1a4c38 100644 --- a/src/story_protocol_python_sdk/resources/IPAsset.py +++ b/src/story_protocol_python_sdk/resources/IPAsset.py @@ -50,7 +50,10 @@ from story_protocol_python_sdk.utils.license_terms import LicenseTerms from story_protocol_python_sdk.utils.sign import Sign from story_protocol_python_sdk.utils.transaction_utils import build_and_send_transaction -from story_protocol_python_sdk.utils.validation import validate_address +from story_protocol_python_sdk.utils.validation import ( + validate_address, + validate_max_rts, +) class IPAsset: @@ -317,7 +320,7 @@ def register_derivative_with_license_tokens( """ try: # Validate max_rts - self._validate_max_rts(max_rts) + validate_max_rts(max_rts) # Validate child IP registration if not self._is_registered(child_ip_id): @@ -825,19 +828,57 @@ def mint_and_register_ip_and_make_derivative( except Exception as e: raise e - def _validate_max_rts(self, max_rts: int): + def mint_and_register_ip_and_make_derivative_with_license_tokens( + self, + spg_nft_contract: Address, + license_token_ids: list[int], + max_rts: int, + recipient: Address | None = None, + allow_duplicates: bool = True, + ip_metadata: IPMetadataInput | None = None, + tx_options: dict | None = None, + ): """ - Validates the maximum number of royalty tokens. + Mint an NFT from a collection and register it as a derivative IP with license tokens. - :param max_rts int: The maximum number of royalty tokens - :raises ValueError: If max_rts is invalid + :param spg_nft_contract Address: The address of the `SPGNFT` collection. + :param license_token_ids list[int]: The IDs of the license tokens to be burned for linking the IP to parent IPs. + :param max_rts int: The maximum number of royalty tokens that can be distributed to the external royalty policies (max: 100,000,000). + :param recipient Address: [Optional] The address to receive the minted NFT. If not provided, the client's own wallet address will be used. + :param allow_duplicates bool: [Optional] Set to true to allow minting an NFT with a duplicate metadata hash. (default: True) + :param ip_metadata IPMetadataInput: [Optional] The desired metadata for the newly minted NFT and newly registered IP. + :param tx_options dict: [Optional] Transaction options. """ - if not isinstance(max_rts, int): - raise ValueError("The maxRts must be a number.") - if max_rts < 0 or max_rts > 100_000_000: - raise ValueError( - "The maxRts must be greater than 0 and less than 100,000,000." + try: + validated_license_token_ids = self._validate_license_token_ids( + license_token_ids + ) + validate_max_rts(max_rts) + response = build_and_send_transaction( + self.web3, + self.account, + self.derivative_workflows_client.build_mintAndRegisterIpAndMakeDerivativeWithLicenseTokens_transaction, + validate_address(spg_nft_contract), + validated_license_token_ids, + ZERO_ADDRESS, + max_rts, + IPMetadata.from_input(ip_metadata).get_validated_data(), + ( + validate_address(recipient) + if recipient is not None + else self.account.address + ), + allow_duplicates, + tx_options=tx_options, ) + ip_registered = self._parse_tx_ip_registered_event(response["tx_receipt"]) + return { + "tx_hash": response["tx_hash"], + "ip_id": ip_registered["ip_id"], + "token_id": ip_registered["token_id"], + } + except Exception as e: + raise e def _validate_derivative_data(self, derivative_data: dict) -> dict: """ diff --git a/src/story_protocol_python_sdk/utils/validation.py b/src/story_protocol_python_sdk/utils/validation.py index 85a603d..01f7d9b 100644 --- a/src/story_protocol_python_sdk/utils/validation.py +++ b/src/story_protocol_python_sdk/utils/validation.py @@ -31,3 +31,14 @@ def get_revenue_share( raise ValueError(f"The {type.value} must be between 0 and 100.") return revShare * 10**6 + + +def validate_max_rts(max_rts: int): + """ + Validates the maximum number of royalty tokens. + + :param max_rts int: The maximum number of royalty tokens + :raises ValueError: If max_rts is invalid + """ + if max_rts < 0 or max_rts > 100_000_000: + raise ValueError("The maxRts must be greater than 0 and less than 100,000,000.") From 91e3582b30729969848ac642338e5027b1941c93 Mon Sep 17 00:00:00 2001 From: Bonnie Date: Fri, 22 Aug 2025 18:03:36 +0800 Subject: [PATCH 3/6] Add integration tests --- tests/integration/conftest.py | 46 ++++++++++++++++++- .../integration/test_integration_ip_asset.py | 44 ++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index df29ef6..133a823 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,12 +1,19 @@ import pytest +from story_protocol_python_sdk.abi.DerivativeWorkflows.DerivativeWorkflows_client import ( + DerivativeWorkflowsClient, +) +from story_protocol_python_sdk.abi.LicenseToken.LicenseToken_client import ( + LicenseTokenClient, +) from story_protocol_python_sdk.story_client import StoryClient from story_protocol_python_sdk.utils.constants import ( ROYALTY_POLICY_LAP_ADDRESS, ZERO_ADDRESS, ) from tests.integration.config.test_config import account, account_2, web3 -from tests.integration.config.utils import MockERC20, get_story_client +from tests.integration.config.utils import MockERC20, approve, get_story_client +from tests.integration.setup_for_integration import PIL_LICENSE_TEMPLATE @pytest.fixture(scope="session") @@ -79,3 +86,40 @@ def parent_ip_and_license_terms(story_client: StoryClient, nft_collection): "parent_ip_id": response["ip_id"], "license_terms_id": response["license_terms_ids"][0], } + + +@pytest.fixture(scope="module") +def mint_and_approve_license_token( + story_client: StoryClient, parent_ip_and_license_terms +): + """ + Fixture to mint and approve license tokens for derivative workflow testing. + + :param story_client: The StoryClient instance. + :param parent_ip_and_license_terms: A dictionary containing the parent IP ID and license terms ID. + :return: A list of license token IDs. + """ + license_token_response = story_client.License.mint_license_tokens( + licensor_ip_id=parent_ip_and_license_terms["parent_ip_id"], + license_template=PIL_LICENSE_TEMPLATE, + license_terms_id=parent_ip_and_license_terms["license_terms_id"], + amount=2, + receiver=account.address, + max_revenue_share=100, + ) + license_token_ids = license_token_response["license_token_ids"] + + for license_token_id in license_token_ids: + approve( + erc20_contract_address=LicenseTokenClient( + story_client.web3 + ).contract.address, + web3=story_client.web3, + account=account, + spender_address=DerivativeWorkflowsClient( + story_client.web3 + ).contract.address, + amount=license_token_id, + ) + + return license_token_ids diff --git a/tests/integration/test_integration_ip_asset.py b/tests/integration/test_integration_ip_asset.py index 6091175..19f3390 100644 --- a/tests/integration/test_integration_ip_asset.py +++ b/tests/integration/test_integration_ip_asset.py @@ -681,3 +681,47 @@ def test_with_custom_value( assert isinstance(response["tx_hash"], str) assert isinstance(response["ip_id"], str) assert isinstance(response["token_id"], int) + + +class TestMintAndRegisterIpAndMakeDerivativeWithLicenseTokens: + def test_default_value( + self, + story_client: StoryClient, + nft_collection, + mint_and_approve_license_token, + ): + license_token_ids = mint_and_approve_license_token + response = story_client.IPAsset.mint_and_register_ip_and_make_derivative_with_license_tokens( + spg_nft_contract=nft_collection, + license_token_ids=[license_token_ids[0]], + max_rts=100000000, + ) + assert response is not None + assert isinstance(response["tx_hash"], str) + assert isinstance(response["ip_id"], str) + assert isinstance(response["token_id"], int) + + def test_with_custom_value( + self, + story_client: StoryClient, + nft_collection, + mint_and_approve_license_token, + ): + license_token_ids = mint_and_approve_license_token + response = story_client.IPAsset.mint_and_register_ip_and_make_derivative_with_license_tokens( + spg_nft_contract=nft_collection, + license_token_ids=[license_token_ids[1]], + max_rts=100000000, + ip_metadata=IPMetadataInput( + ip_metadata_uri="https://example.com/metadata/custom-value.json", + ip_metadata_hash=web3.keccak(text="custom-value-metadata"), + nft_metadata_uri="https://example.com/metadata/custom-value.json", + nft_metadata_hash=web3.keccak(text="custom-value-metadata"), + ), + recipient=account_2.address, + allow_duplicates=False, + ) + assert response is not None + assert isinstance(response["tx_hash"], str) + assert isinstance(response["ip_id"], str) + assert isinstance(response["token_id"], int) From 499b30e2b4d50a820ab718136d4a4ffb53c6001a Mon Sep 17 00:00:00 2001 From: Bonnie Date: Mon, 25 Aug 2025 11:02:06 +0800 Subject: [PATCH 4/6] Add unit tests --- tests/integration/test_integration_group.py | 2 - tests/unit/conftest.py | 5 +- tests/unit/fixtures/data.py | 1 + tests/unit/resources/test_ip_asset.py | 141 +++++++++++++++++++- 4 files changed, 143 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_integration_group.py b/tests/integration/test_integration_group.py index fce3a17..e5d645e 100644 --- a/tests/integration/test_integration_group.py +++ b/tests/integration/test_integration_group.py @@ -525,8 +525,6 @@ def test_collect_and_distribute_group_royalties( group_ip_id=group_ip_id, currency_tokens=[MockERC20], member_ip_ids=ip_ids ) - print("response is ", response) - assert "tx_hash" in response assert isinstance(response["tx_hash"], str) assert len(response["tx_hash"]) > 0 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 08b7793..88c3b58 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -4,14 +4,13 @@ from eth_account import Account from web3 import Web3 -from tests.unit.fixtures.data import ADDRESS, TX_HASH +from tests.unit.fixtures.data import ACCOUNT_ADDRESS, ADDRESS, TX_HASH @pytest.fixture(scope="package") def mock_account(): account = MagicMock() - account.address = "0xF60cBF0Ea1A61567F1dDaf79A6219D20d189155c" - # Create a mock signed transaction object with raw_transaction attribute + account.address = ACCOUNT_ADDRESS # Create a mock signed transaction object with raw_transaction attribute mock_signed_txn = MagicMock() mock_signed_txn.raw_transaction = b"raw_transaction_bytes" diff --git a/tests/unit/fixtures/data.py b/tests/unit/fixtures/data.py index 9e2505d..891b731 100644 --- a/tests/unit/fixtures/data.py +++ b/tests/unit/fixtures/data.py @@ -34,3 +34,4 @@ "expect_minimum_group_reward_share": 10, "expect_group_reward_pool": ADDRESS, } +ACCOUNT_ADDRESS = "0xF60cBF0Ea1A61567F1dDaf79A6219D20d189155c" diff --git a/tests/unit/resources/test_ip_asset.py b/tests/unit/resources/test_ip_asset.py index 6a888aa..dbd594c 100644 --- a/tests/unit/resources/test_ip_asset.py +++ b/tests/unit/resources/test_ip_asset.py @@ -9,6 +9,7 @@ from story_protocol_python_sdk.utils.ip_metadata import IPMetadata, IPMetadataInput from tests.integration.config.utils import ZERO_ADDRESS from tests.unit.fixtures.data import ( + ACCOUNT_ADDRESS, ADDRESS, CHAIN_ID, IP_ID, @@ -389,7 +390,6 @@ def test_default_value_when_not_provided( license_terms_ids=[1, 2], ) call_args = mock_build_registerDerivative_transaction.call_args[0] - print(call_args) assert ( call_args[3] == "0x1234567890123456789012345678901234567890" ) # license_template @@ -540,3 +540,142 @@ def test_with_custom_value( } assert mock_build_transaction.call_args[0][3] == ADDRESS # recipient assert not mock_build_transaction.call_args[0][4] # allowDuplicates + + +@pytest.fixture(scope="class") +def mock_owner_of(ip_asset: IPAsset): + def _mock(owner: str = ACCOUNT_ADDRESS): + return patch.object( + ip_asset.license_token_client, "ownerOf", return_value=owner + ) + + return _mock + + +class TestMintAndRegisterIpAndMakeDerivativeWithLicenseTokens: + + def test_throw_error_when_license_token_ids_is_not_owned_by_caller( + self, + ip_asset: IPAsset, + mock_owner_of, + ): + with mock_owner_of("0x1234567890123456789012345678901234567890"): + with pytest.raises( + ValueError, match="License token id 1 must be owned by the caller." + ): + ip_asset.mint_and_register_ip_and_make_derivative_with_license_tokens( + spg_nft_contract=ADDRESS, + license_token_ids=[1, 2, 3], + max_rts=100, + ) + + def test_throw_error_when_max_rts_is_invalid( + self, + ip_asset: IPAsset, + mock_owner_of, + ): + with mock_owner_of(): + with pytest.raises( + ValueError, + match="The maxRts must be greater than 0 and less than 100,000,000.", + ): + ip_asset.mint_and_register_ip_and_make_derivative_with_license_tokens( + spg_nft_contract=ADDRESS, + license_token_ids=[1, 2, 3], + max_rts=1000000000000000000, + ) + + def test_success_when_default_values_not_provided( + self, + ip_asset: IPAsset, + mock_owner_of, + mock_parse_ip_registered_event, + ): + with mock_owner_of(): + with patch.object( + ip_asset.derivative_workflows_client, + "build_mintAndRegisterIpAndMakeDerivativeWithLicenseTokens_transaction", + return_value={"tx_hash": TX_HASH.hex()}, + ) as mock_build_transaction: + with mock_parse_ip_registered_event(): + result = ip_asset.mint_and_register_ip_and_make_derivative_with_license_tokens( + spg_nft_contract=ADDRESS, + license_token_ids=[1, 2], + max_rts=100, + ) + assert mock_build_transaction.call_args[0][:7] == ( + ADDRESS, + [1, 2], + ZERO_ADDRESS, + 100, + IPMetadata.from_input().get_validated_data(), + ACCOUNT_ADDRESS, + True, + ) + assert result["tx_hash"] == TX_HASH.hex() + assert result["ip_id"] == IP_ID + assert result["token_id"] == 3 + + def test_with_custom_value( + self, + ip_asset: IPAsset, + mock_owner_of, + mock_parse_ip_registered_event, + ): + with mock_owner_of(): + with patch.object( + ip_asset.derivative_workflows_client, + "build_mintAndRegisterIpAndMakeDerivativeWithLicenseTokens_transaction", + return_value={"tx_hash": TX_HASH.hex()}, + ) as mock_build_transaction: + with mock_parse_ip_registered_event(): + result = ip_asset.mint_and_register_ip_and_make_derivative_with_license_tokens( + spg_nft_contract=ADDRESS, + license_token_ids=[1, 2, 3], + max_rts=100, + ip_metadata=IPMetadataInput( + ip_metadata_uri="https://example.com/metadata/custom-value.json", + ip_metadata_hash=HexStr("ip_metadata_hash"), + nft_metadata_uri="https://example.com/metadata/custom-value.json", + nft_metadata_hash=HexStr("nft_metadata_hash"), + ), + recipient=ADDRESS, + allow_duplicates=False, + ) + assert mock_build_transaction.call_args[0][:7] == ( + ADDRESS, + [1, 2, 3], + ZERO_ADDRESS, + 100, + IPMetadata.from_input( + IPMetadataInput( + ip_metadata_uri="https://example.com/metadata/custom-value.json", + ip_metadata_hash=HexStr("ip_metadata_hash"), + nft_metadata_uri="https://example.com/metadata/custom-value.json", + nft_metadata_hash=HexStr("nft_metadata_hash"), + ), + ).get_validated_data(), + ADDRESS, + False, + ) + assert result["tx_hash"] == TX_HASH.hex() + assert result["ip_id"] == IP_ID + assert result["token_id"] == 3 + + def test_throw_error_when_transaction_failed( + self, + ip_asset: IPAsset, + mock_owner_of, + ): + with mock_owner_of(): + with patch.object( + ip_asset.derivative_workflows_client, + "build_mintAndRegisterIpAndMakeDerivativeWithLicenseTokens_transaction", + side_effect=Exception("Transaction failed."), + ): + with pytest.raises(Exception, match="Transaction failed."): + ip_asset.mint_and_register_ip_and_make_derivative_with_license_tokens( + spg_nft_contract=ADDRESS, + license_token_ids=[1, 2, 3], + max_rts=100, + ) From 260332619dc7ea68f935969b5dd5844c04d1b9f7 Mon Sep 17 00:00:00 2001 From: Bonnie Date: Mon, 25 Aug 2025 13:33:26 +0800 Subject: [PATCH 5/6] Update integration tests to include minting and registering of a second parent IP with license tokens. --- .../resources/IPAsset.py | 5 +- .../integration/test_integration_ip_asset.py | 78 ++++++++++++++++++- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/story_protocol_python_sdk/resources/IPAsset.py b/src/story_protocol_python_sdk/resources/IPAsset.py index b1a4c38..a35aeef 100644 --- a/src/story_protocol_python_sdk/resources/IPAsset.py +++ b/src/story_protocol_python_sdk/resources/IPAsset.py @@ -837,7 +837,7 @@ def mint_and_register_ip_and_make_derivative_with_license_tokens( allow_duplicates: bool = True, ip_metadata: IPMetadataInput | None = None, tx_options: dict | None = None, - ): + ) -> RegistrationResponse: """ Mint an NFT from a collection and register it as a derivative IP with license tokens. @@ -848,6 +848,7 @@ def mint_and_register_ip_and_make_derivative_with_license_tokens( :param allow_duplicates bool: [Optional] Set to true to allow minting an NFT with a duplicate metadata hash. (default: True) :param ip_metadata IPMetadataInput: [Optional] The desired metadata for the newly minted NFT and newly registered IP. :param tx_options dict: [Optional] Transaction options. + :return RegistrationResponse: Dictionary with the tx hash, IP ID and token ID. """ try: validated_license_token_ids = self._validate_license_token_ids( @@ -917,7 +918,7 @@ def _validate_derivative_data(self, derivative_data: dict) -> dict: if internal_data["maxMintingFee"] < 0: raise ValueError("The maxMintingFee must be greater than 0.") - self._validate_max_rts(internal_data["maxRts"]) + validate_max_rts(internal_data["maxRts"]) for parent_id, terms_id in zip( internal_data["parentIpIds"], internal_data["licenseTermsIds"] diff --git a/tests/integration/test_integration_ip_asset.py b/tests/integration/test_integration_ip_asset.py index 19f3390..9a8bdc0 100644 --- a/tests/integration/test_integration_ip_asset.py +++ b/tests/integration/test_integration_ip_asset.py @@ -1,11 +1,17 @@ -# tests/integration/test_integration_ip_asset.py - import pytest +from story_protocol_python_sdk.abi.DerivativeWorkflows.DerivativeWorkflows_client import ( + DerivativeWorkflowsClient, +) +from story_protocol_python_sdk.abi.LicenseToken.LicenseToken_client import ( + LicenseTokenClient, +) from story_protocol_python_sdk.story_client import StoryClient +from story_protocol_python_sdk.utils.constants import ROYALTY_POLICY_LAP_ADDRESS from story_protocol_python_sdk.utils.derivative_data import DerivativeDataInput from story_protocol_python_sdk.utils.ip_metadata import IPMetadataInput from tests.integration.config.test_config import account_2 +from tests.integration.config.utils import approve from .setup_for_integration import ( PIL_LICENSE_TEMPLATE, @@ -690,10 +696,74 @@ def test_default_value( nft_collection, mint_and_approve_license_token, ): - license_token_ids = mint_and_approve_license_token + # Get second parent ip and license terms + second_parent_ip_and_license_terms = ( + story_client.IPAsset.mint_and_register_ip_asset_with_pil_terms( + spg_nft_contract=nft_collection, + terms=[ + { + "terms": { + "transferable": True, + "royalty_policy": ROYALTY_POLICY_LAP_ADDRESS, + "default_minting_fee": 0, + "expiration": 0, + "commercial_use": True, + "commercial_attribution": False, + "commercializer_checker": ZERO_ADDRESS, + "commercializer_checker_data": ZERO_ADDRESS, + "commercial_rev_share": 50, + "commercial_rev_ceiling": 0, + "derivatives_allowed": True, + "derivatives_attribution": True, + "derivatives_approval": False, + "derivatives_reciprocal": True, + "derivative_rev_ceiling": 0, + "currency": MockERC20, + "uri": "", + }, + "licensing_config": { + "is_set": True, + "minting_fee": 0, + "hook_data": ZERO_ADDRESS, + "licensing_hook": ZERO_ADDRESS, + "commercial_rev_share": 0, + "disabled": False, + "expect_minimum_group_reward_share": 0, + "expect_group_reward_pool": ZERO_ADDRESS, + }, + } + ], + allow_duplicates=True, + ) + ) + # Mint license tokens for second parent ip + second_license_token_ids = story_client.License.mint_license_tokens( + licensor_ip_id=second_parent_ip_and_license_terms["ip_id"], + license_template=PIL_LICENSE_TEMPLATE, + license_terms_id=second_parent_ip_and_license_terms["license_terms_ids"][0], + amount=1, + receiver=account.address, + max_revenue_share=100, + ) + # Approve license tokens for derivative workflows + approve( + erc20_contract_address=LicenseTokenClient( + story_client.web3 + ).contract.address, + web3=story_client.web3, + account=account, + spender_address=DerivativeWorkflowsClient( + story_client.web3 + ).contract.address, + amount=second_license_token_ids["license_token_ids"][0], + ) + # Mint and register ip and make derivative with license tokens response = story_client.IPAsset.mint_and_register_ip_and_make_derivative_with_license_tokens( spg_nft_contract=nft_collection, - license_token_ids=[license_token_ids[0]], + license_token_ids=[ + mint_and_approve_license_token[0], + second_license_token_ids["license_token_ids"][0], + ], max_rts=100000000, ) assert response is not None From 089fc3d930aca64769c3f3899060ea43f1f9f91d Mon Sep 17 00:00:00 2001 From: Bonnie Date: Mon, 25 Aug 2025 14:12:31 +0800 Subject: [PATCH 6/6] Refactor IPAsset class to return RegistrationResponse instead of dictionary for IP registration methods. Update integration test configuration to allow duplicates. --- .../resources/IPAsset.py | 20 +++++++++---------- tests/integration/conftest.py | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/story_protocol_python_sdk/resources/IPAsset.py b/src/story_protocol_python_sdk/resources/IPAsset.py index a35aeef..f6f97da 100644 --- a/src/story_protocol_python_sdk/resources/IPAsset.py +++ b/src/story_protocol_python_sdk/resources/IPAsset.py @@ -820,11 +820,11 @@ def mint_and_register_ip_and_make_derivative( tx_options=tx_options, ) ip_registered = self._parse_tx_ip_registered_event(response["tx_receipt"]) - return { - "tx_hash": response["tx_hash"], - "ip_id": ip_registered["ip_id"], - "token_id": ip_registered["token_id"], - } + return RegistrationResponse( + tx_hash=response["tx_hash"], + ip_id=ip_registered["ip_id"], + token_id=ip_registered["token_id"], + ) except Exception as e: raise e @@ -873,11 +873,11 @@ def mint_and_register_ip_and_make_derivative_with_license_tokens( tx_options=tx_options, ) ip_registered = self._parse_tx_ip_registered_event(response["tx_receipt"]) - return { - "tx_hash": response["tx_hash"], - "ip_id": ip_registered["ip_id"], - "token_id": ip_registered["token_id"], - } + return RegistrationResponse( + tx_hash=response["tx_hash"], + ip_id=ip_registered["ip_id"], + token_id=ip_registered["token_id"], + ) except Exception as e: raise e diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 133a823..5df41ba 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -81,6 +81,7 @@ def parent_ip_and_license_terms(story_client: StoryClient, nft_collection): }, } ], + allow_duplicates=True, ) return { "parent_ip_id": response["ip_id"],