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
2 changes: 2 additions & 0 deletions src/story_protocol_python_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .types.resource.IPAsset import (
LicenseTermsDataInput,
RegisterAndAttachAndDistributeRoyaltyTokensResponse,
RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse,
RegisterPILTermsAndAttachResponse,
RegistrationResponse,
RegistrationWithRoyaltyVaultAndLicenseTermsResponse,
Expand Down Expand Up @@ -52,6 +53,7 @@
"RegistrationWithRoyaltyVaultResponse",
"RegistrationWithRoyaltyVaultAndLicenseTermsResponse",
"RegisterAndAttachAndDistributeRoyaltyTokensResponse",
"RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse",
"LicenseTermsDataInput",
"ClaimRewardsResponse",
"ClaimReward",
Expand Down
124 changes: 109 additions & 15 deletions src/story_protocol_python_sdk/resources/IPAsset.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from story_protocol_python_sdk.types.resource.IPAsset import (
LicenseTermsDataInput,
RegisterAndAttachAndDistributeRoyaltyTokensResponse,
RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse,
RegisterPILTermsAndAttachResponse,
RegistrationResponse,
RegistrationWithRoyaltyVaultAndLicenseTermsResponse,
Expand Down Expand Up @@ -222,12 +223,10 @@ def register(
],
)

signature = self.web3.to_bytes(hexstr=signature_response["signature"])

req_object["sigMetadata"] = {
"signer": self.web3.to_checksum_address(self.account.address),
"deadline": calculated_deadline,
"signature": signature,
"signature": signature_response["signature"],
}

response = build_and_send_transaction(
Expand Down Expand Up @@ -681,9 +680,7 @@ def register_ip_and_attach_pil_terms(
{
"signer": self.web3.to_checksum_address(self.account.address),
"deadline": calculated_deadline,
"signature": self.web3.to_bytes(
hexstr=signature_response["signature"]
),
"signature": signature_response["signature"],
},
tx_options=tx_options,
)
Expand Down Expand Up @@ -959,9 +956,7 @@ def register_ip_and_make_derivative_with_license_tokens(
{
"signer": self.web3.to_checksum_address(self.account.address),
"deadline": calculated_deadline,
"signature": self.web3.to_bytes(
hexstr=signature_response["signature"]
),
"signature": signature_response["signature"],
},
tx_options=tx_options,
)
Expand Down Expand Up @@ -1099,6 +1094,109 @@ def mint_and_register_ip_and_make_derivative_and_distribute_royalty_tokens(
f"Failed to mint, register IP, make derivative and distribute royalty tokens: {str(e)}"
) from e

def register_derivative_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
self,
nft_contract: Address,
token_id: int,
deriv_data: DerivativeDataInput,
royalty_shares: list[RoyaltyShareInput],
ip_metadata: IPMetadataInput | None = None,
deadline: int = 1000,
tx_options: dict | None = None,
) -> RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse:
"""
Register the given NFT as a derivative IP, attach license terms from parent IPs, and distribute royalty tokens.
In order to successfully distribute royalty tokens, the first license terms attached to the IP must be a commercial license.

:param nft_contract Address: The address of the NFT collection.
:param token_id int: The ID of the NFT.
:param deriv_data `DerivativeDataInput`: The derivative data to be used for register derivative.
:param royalty_shares `list[RoyaltyShareInput]`: Authors of the IP and their shares of the royalty tokens.
:param ip_metadata `IPMetadataInput`: [Optional] The metadata for the newly registered IP.
:param deadline int: [Optional] The deadline for the signature in seconds. (default: 1000 seconds)
:param tx_options dict: [Optional] Transaction options.
:return `RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse`: Response with tx hash, IP ID, token ID, royalty vault address, and distribute royalty tokens transaction hash.
"""
try:
nft_contract = validate_address(nft_contract)
ip_id = self._get_ip_id(nft_contract, token_id)
if self._is_registered(ip_id):
raise ValueError(
f"The NFT with id {token_id} is already registered as IP."
)

validated_deriv_data = DerivativeData.from_input(
web3=self.web3, input_data=deriv_data
).get_validated_data()
calculated_deadline = self.sign_util.get_deadline(deadline=deadline)
royalty_shares_obj = get_royalty_shares(royalty_shares)

signature_response = self.sign_util.get_permission_signature(
ip_id=ip_id,
deadline=calculated_deadline,
state=self.web3.to_bytes(hexstr=HexStr(ZERO_HASH)),
permissions=[
{
"ipId": ip_id,
"signer": self.royalty_token_distribution_workflows_client.contract.address,
"to": self.core_metadata_module_client.contract.address,
"permission": AccessPermission.ALLOW,
"func": "setAll(address,string,bytes32,bytes32)",
},
{
"ipId": ip_id,
"signer": self.royalty_token_distribution_workflows_client.contract.address,
"to": self.licensing_module_client.contract.address,
"permission": AccessPermission.ALLOW,
"func": "registerDerivative(address,address[],uint256[],address,bytes,uint256,uint32,uint32)",
},
],
)

response = build_and_send_transaction(
self.web3,
self.account,
self.royalty_token_distribution_workflows_client.build_registerIpAndMakeDerivativeAndDeployRoyaltyVault_transaction,
nft_contract,
token_id,
IPMetadata.from_input(ip_metadata).get_validated_data(),
validated_deriv_data,
{
"signer": self.web3.to_checksum_address(self.account.address),
"deadline": calculated_deadline,
"signature": signature_response["signature"],
},
tx_options=tx_options,
)

ip_registered = self._parse_tx_ip_registered_event(response["tx_receipt"])
royalty_vault = self.get_royalty_vault_address_by_ip_id(
response["tx_receipt"],
ip_registered["ip_id"],
)

# Distribute royalty tokens
distribute_tx_hash = self._distribute_royalty_tokens(
ip_id=ip_registered["ip_id"],
royalty_shares=royalty_shares_obj["royalty_shares"],
royalty_vault=royalty_vault,
total_amount=royalty_shares_obj["total_amount"],
tx_options=tx_options,
deadline=calculated_deadline,
)

return RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse(
tx_hash=response["tx_hash"],
ip_id=ip_registered["ip_id"],
token_id=ip_registered["token_id"],
royalty_vault=royalty_vault,
distribute_royalty_tokens_tx_hash=distribute_tx_hash,
)
except Exception as e:
raise ValueError(
f"Failed to register derivative IP and distribute royalty tokens: {str(e)}"
) from e

def register_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
self,
nft_contract: Address,
Expand Down Expand Up @@ -1175,9 +1273,7 @@ def register_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
{
"signer": self.web3.to_checksum_address(self.account.address),
"deadline": calculated_deadline,
"signature": self.web3.to_bytes(
hexstr=signature_response["signature"]
),
"signature": signature_response["signature"],
},
tx_options=tx_options,
)
Expand Down Expand Up @@ -1431,9 +1527,7 @@ def _distribute_royalty_tokens(
{
"signer": self.web3.to_checksum_address(self.account.address),
"deadline": deadline,
"signature": self.web3.to_bytes(
hexstr=signature_response["signature"]
),
"signature": signature_response["signature"],
},
tx_options=tx_options,
)
Expand Down
17 changes: 17 additions & 0 deletions src/story_protocol_python_sdk/types/resource/IPAsset.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@ class RegisterAndAttachAndDistributeRoyaltyTokensResponse(
distribute_royalty_tokens_tx_hash: HexStr


class RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse(
RegistrationResponse
):
"""
Response structure for derivative IP and attach PIL terms and distribute royalty tokens.

Extends `RegistrationResponse` with distribute royalty tokens transaction hash.

Attributes:
distribute_royalty_tokens_tx_hash: The transaction hash of the distribute royalty tokens transaction.
royalty_vault: The royalty vault address of the registered IP asset.
"""

distribute_royalty_tokens_tx_hash: HexStr
royalty_vault: Address


@dataclass
class LicenseTermsDataInput:
"""
Expand Down
37 changes: 37 additions & 0 deletions tests/integration/test_integration_ip_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,43 @@ def test_register_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
and response["distribute_royalty_tokens_tx_hash"]
)

def test_register_derivative_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
self, story_client: StoryClient, nft_collection, parent_ip_and_license_terms
):
"""Test registering an existing NFT as derivative IP and distributing royalty tokens with all optional parameters"""
# Mint an NFT first
token_id = mint_by_spg(nft_collection, story_client.web3, story_client.account)

royalty_shares = [
RoyaltyShareInput(recipient=account.address, percentage=40.0),
RoyaltyShareInput(recipient=account_2.address, percentage=60.0),
]

response = story_client.IPAsset.register_derivative_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
nft_contract=nft_collection,
token_id=token_id,
deriv_data=DerivativeDataInput(
parent_ip_ids=[parent_ip_and_license_terms["parent_ip_id"]],
license_terms_ids=[parent_ip_and_license_terms["license_terms_id"]],
max_minting_fee=10000,
max_rts=10,
max_revenue_share=100,
),
royalty_shares=royalty_shares,
ip_metadata=COMMON_IP_METADATA,
deadline=1000,
)
assert isinstance(response["tx_hash"], str) and response["tx_hash"]
assert isinstance(response["ip_id"], str) and response["ip_id"]
assert (
isinstance(response["token_id"], int) and response["token_id"] == token_id
)
assert isinstance(response["royalty_vault"], str) and response["royalty_vault"]
assert (
isinstance(response["distribute_royalty_tokens_tx_hash"], str)
and response["distribute_royalty_tokens_tx_hash"]
)


class TestIPAssetMint:
@pytest.fixture(scope="module")
Expand Down
Loading
Loading