Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
):
Expand Down
76 changes: 59 additions & 17 deletions src/story_protocol_python_sdk/resources/IPAsset.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -817,27 +820,66 @@ 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

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,
) -> RegistrationResponse:
"""
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.
:return RegistrationResponse: Dictionary with the tx hash, IP ID and token ID.
"""
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 RegistrationResponse(
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:
"""
Expand Down Expand Up @@ -876,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"]
Expand Down
3 changes: 2 additions & 1 deletion src/story_protocol_python_sdk/scripts/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@
"contract_address": "0x9e2d496f72C547C2C535B167e06ED8729B374a4f",
"functions": [
"registerIpAndMakeDerivative",
"mintAndRegisterIpAndMakeDerivative"
"mintAndRegisterIpAndMakeDerivative",
"mintAndRegisterIpAndMakeDerivativeWithLicenseTokens"
]
}
]
Expand Down
11 changes: 11 additions & 0 deletions src/story_protocol_python_sdk/utils/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
47 changes: 46 additions & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -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")
Expand Down Expand Up @@ -74,8 +81,46 @@ def parent_ip_and_license_terms(story_client: StoryClient, nft_collection):
},
}
],
allow_duplicates=True,
)
return {
"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
2 changes: 0 additions & 2 deletions tests/integration/test_integration_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 116 additions & 2 deletions tests/integration/test_integration_ip_asset.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -681,3 +687,111 @@ 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,
):
# 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=[
mint_and_approve_license_token[0],
second_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)
Loading
Loading