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 @@ -21,6 +21,7 @@
MintNFT,
RegisterAndAttachAndDistributeRoyaltyTokensResponse,
RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse,
RegisterDerivativeIpAssetResponse,
RegisteredIP,
RegisterIpAssetResponse,
RegisterPILTermsAndAttachResponse,
Expand Down Expand Up @@ -75,6 +76,7 @@
"MintNFT",
"MintedNFT",
"RegisterIpAssetResponse",
"RegisterDerivativeIpAssetResponse",
# Constants
"ZERO_ADDRESS",
"ZERO_HASH",
Expand Down
237 changes: 232 additions & 5 deletions src/story_protocol_python_sdk/resources/IPAsset.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Module for handling IP Account operations and transactions."""

from dataclasses import asdict, is_dataclass
from typing import cast

from ens.ens import Address, HexStr
from typing_extensions import deprecated
Expand Down Expand Up @@ -59,6 +60,7 @@
MintNFT,
RegisterAndAttachAndDistributeRoyaltyTokensResponse,
RegisterDerivativeIPAndAttachAndDistributeRoyaltyTokensResponse,
RegisterDerivativeIpAssetResponse,
RegisteredIP,
RegisterIpAssetResponse,
RegisterPILTermsAndAttachResponse,
Expand All @@ -68,6 +70,7 @@
)
from story_protocol_python_sdk.types.resource.Royalty import RoyaltyShareInput
from story_protocol_python_sdk.utils.constants import (
DEADLINE,
MAX_ROYALTY_TOKEN,
ZERO_ADDRESS,
ZERO_HASH,
Expand Down Expand Up @@ -769,6 +772,7 @@ def register_ip_and_attach_pil_terms(
except Exception as e:
raise e

@deprecated("Deprecated: Use register_derivative_ip_asset instead.")
def register_derivative_ip(
self,
nft_contract: str,
Expand Down Expand Up @@ -847,11 +851,16 @@ def register_derivative_ip(
0
]

return {"tx_hash": response["tx_hash"], "ip_id": ip_registered["ip_id"]}
return {
"tx_hash": response["tx_hash"],
"ip_id": ip_registered["ip_id"],
"token_id": ip_registered["token_id"],
}

except Exception as e:
raise e

@deprecated("Deprecated: Use register_derivative_ip_asset instead.")
def mint_and_register_ip_and_make_derivative(
self,
spg_nft_contract: str,
Expand Down Expand Up @@ -899,11 +908,12 @@ def mint_and_register_ip_and_make_derivative(
except Exception as e:
raise e

@deprecated("Deprecated: Use register_derivative_ip_asset instead.")
def mint_and_register_ip_and_make_derivative_with_license_tokens(
self,
spg_nft_contract: Address,
license_token_ids: list[int],
max_rts: int,
max_rts: int = MAX_ROYALTY_TOKEN,
recipient: Address | None = None,
allow_duplicates: bool = True,
ip_metadata: IPMetadataInput | None = None,
Expand All @@ -914,7 +924,7 @@ def mint_and_register_ip_and_make_derivative_with_license_tokens(

: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 max_rts int: The maximum number of royalty tokens that can be distributed to the external royalty policies (default: 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.
Expand Down Expand Up @@ -950,6 +960,7 @@ def mint_and_register_ip_and_make_derivative_with_license_tokens(
except Exception as e:
raise e

@deprecated("Deprecated: Use register_derivative_ip_asset instead.")
def register_ip_and_make_derivative_with_license_tokens(
self,
nft_contract: str,
Expand Down Expand Up @@ -1116,6 +1127,7 @@ def mint_and_register_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
f"Failed to mint, register IP, attach PIL terms and distribute royalty tokens: {str(e)}"
) from e

@deprecated("Deprecated: Use register_derivative_ip_asset instead.")
def mint_and_register_ip_and_make_derivative_and_distribute_royalty_tokens(
self,
spg_nft_contract: Address,
Expand Down Expand Up @@ -1164,7 +1176,6 @@ def mint_and_register_ip_and_make_derivative_and_distribute_royalty_tokens(
response["tx_receipt"],
ip_registered["ip_id"],
)

return RegistrationWithRoyaltyVaultResponse(
tx_hash=response["tx_hash"],
ip_id=ip_registered["ip_id"],
Expand All @@ -1176,6 +1187,7 @@ 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

@deprecated("Deprecated: Use register_derivative_ip_asset instead.")
def register_derivative_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
self,
nft_contract: Address,
Expand Down Expand Up @@ -1475,7 +1487,7 @@ def register_ip_asset(
license_terms_data: list[LicenseTermsDataInput] | None = None,
royalty_shares: list[RoyaltyShareInput] | None = None,
ip_metadata: IPMetadataInput | None = None,
deadline: int | None = None,
deadline: int = DEADLINE,
tx_options: dict | None = None,
) -> RegisterIpAssetResponse:
"""
Expand Down Expand Up @@ -1665,6 +1677,221 @@ def _handle_mint_nft_registration(
token_id=basic_result["token_id"],
)

def register_derivative_ip_asset(
self,
nft: MintNFT | MintedNFT,
deriv_data: DerivativeDataInput | None = None,
license_token_ids: list[int] | None = None,
royalty_shares: list[RoyaltyShareInput] | None = None,
max_rts: int = MAX_ROYALTY_TOKEN,
deadline: int = DEADLINE,
ip_metadata: IPMetadataInput | None = None,
tx_options: dict | None = None,
) -> RegisterDerivativeIpAssetResponse:
"""
Register a derivative IP asset, supporting both minted and mint-on-demand NFTs,
with optional `deriv_data`, `royalty_shares` and `license_token_ids`.

This method automatically selects and calls the appropriate workflow from 6 available
methods based on your input parameters:

For already minted NFT (type="minted"):
- With `deriv_data` + `royalty_shares`:
`registerIpAndMakeDerivativeAndDeployRoyaltyVault` (contract method)
+ `distributeRoyaltyTokens` (contract method)
- With `deriv_data` only: `registerIpAndMakeDerivative` (contract method)
- With `license_token_ids` only: `registerIpAndMakeDerivativeWithLicenseTokens` (contract method)

For new minted NFT (type="mint"):
- With `deriv_data` + `royalty_shares`: `mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens` (contract method)
- With `deriv_data` only: `mintAndRegisterIpAndMakeDerivative` (contract method)
- With `license_token_ids` only: `mintAndRegisterIpAndMakeDerivativeWithLicenseTokens` (contract method)

:param nft `MintNFT` | `MintedNFT`: The NFT to be registered as a derivative IP asset.
- For `MintNFT`: Mint a new NFT from an SPG NFT contract.
- For `MintedNFT`: Register an already minted NFT.
:param deriv_data `DerivativeDataInput`: [Optional] The derivative data containing parent IP information and licensing terms.
Can be used independently or together with `royalty_shares` for royalty distribution.
:param license_token_ids list[int]: [Optional] The IDs of the license tokens to be burned for linking the IP to parent IPs.
:param royalty_shares `list[RoyaltyShareInput]`: [Optional] Authors of the IP and their shares of the royalty tokens.
Can only be specified when `deriv_data` is also provided.
:param max_rts int: [Optional] The maximum number of royalty tokens that can be distributed to the external royalty policies (max: 100,000,000). (default: 100,000,000)
:param ip_metadata `IPMetadataInput`: [Optional] The desired metadata for the newly registered IP.
:param deadline int: [Optional] Signature deadline in seconds. (default: 1000 seconds)
:param tx_options dict: [Optional] Transaction options.
:return `RegisterDerivativeIpAssetResponse`: Response with transaction hash, IP ID, token ID, and optionally royalty vault and distribute royalty tokens transaction hash.
:raises ValueError: If `royalty_shares` is provided without `deriv_data`.
:raises ValueError: If neither `deriv_data` nor `license_token_ids` are provided.
"""
try:
if royalty_shares and not deriv_data:
raise ValueError(
"deriv_data must be provided when royalty_shares are provided."
)

has_deriv_data = deriv_data is not None
has_license_tokens = (
license_token_ids is not None and len(license_token_ids) > 0
)

if not has_deriv_data and not has_license_tokens:
raise ValueError(
"either deriv_data or license_token_ids must be provided."
)

if nft.type == "minted":
return self._handle_minted_nft_derivative_registration(
nft=nft,
deriv_data=deriv_data,
license_token_ids=license_token_ids,
royalty_shares=royalty_shares,
max_rts=max_rts,
ip_metadata=ip_metadata,
deadline=deadline,
tx_options=tx_options,
)

return self._handle_mint_nft_derivative_registration(
nft=nft,
deriv_data=deriv_data,
license_token_ids=license_token_ids,
royalty_shares=royalty_shares,
max_rts=max_rts,
ip_metadata=ip_metadata,
tx_options=tx_options,
)

except Exception as e:
raise ValueError(f"Failed to register derivative IP Asset: {str(e)}") from e

def _handle_minted_nft_derivative_registration(
self,
nft: MintedNFT,
deriv_data: DerivativeDataInput | None,
license_token_ids: list[int] | None,
royalty_shares: list[RoyaltyShareInput] | None,
max_rts: int,
ip_metadata: IPMetadataInput | None,
deadline: int,
tx_options: dict | None,
) -> RegisterDerivativeIpAssetResponse:
"""
Handle derivative registration for already minted NFTs.
"""
if royalty_shares and deriv_data:
royalty_result = self.register_derivative_ip_and_attach_pil_terms_and_distribute_royalty_tokens(
nft_contract=nft.nft_contract,
token_id=nft.token_id,
deriv_data=deriv_data,
royalty_shares=royalty_shares,
ip_metadata=ip_metadata,
deadline=deadline,
tx_options=tx_options,
)
return RegisterDerivativeIpAssetResponse(
tx_hash=royalty_result["tx_hash"],
ip_id=royalty_result["ip_id"],
token_id=royalty_result["token_id"],
royalty_vault=royalty_result["royalty_vault"],
distribute_royalty_tokens_tx_hash=royalty_result[
"distribute_royalty_tokens_tx_hash"
],
)

if deriv_data:
deriv_result = self.register_derivative_ip(
nft_contract=nft.nft_contract,
token_id=nft.token_id,
deriv_data=deriv_data,
metadata=ip_metadata,
deadline=deadline,
tx_options=tx_options,
)
return RegisterDerivativeIpAssetResponse(
tx_hash=deriv_result["tx_hash"],
ip_id=deriv_result["ip_id"],
token_id=deriv_result["token_id"],
)

# Use license_token_ids
token_result = self.register_ip_and_make_derivative_with_license_tokens(
nft_contract=nft.nft_contract,
token_id=nft.token_id,
license_token_ids=cast(list[int], license_token_ids),
max_rts=max_rts,
deadline=deadline,
ip_metadata=ip_metadata,
tx_options=tx_options,
)
return RegisterDerivativeIpAssetResponse(
tx_hash=token_result["tx_hash"],
ip_id=token_result["ip_id"],
token_id=token_result["token_id"],
)

def _handle_mint_nft_derivative_registration(
self,
nft: MintNFT,
deriv_data: DerivativeDataInput | None,
license_token_ids: list[int] | None,
royalty_shares: list[RoyaltyShareInput] | None,
max_rts: int,
ip_metadata: IPMetadataInput | None,
tx_options: dict | None,
) -> RegisterDerivativeIpAssetResponse:
"""
Handle derivative registration for minting new NFTs.
"""
if royalty_shares and deriv_data:
royalty_result = self.mint_and_register_ip_and_make_derivative_and_distribute_royalty_tokens(
spg_nft_contract=nft.spg_nft_contract,
deriv_data=deriv_data,
royalty_shares=royalty_shares,
ip_metadata=ip_metadata,
recipient=nft.recipient,
allow_duplicates=nft.allow_duplicates,
tx_options=tx_options,
)
return RegisterDerivativeIpAssetResponse(
tx_hash=royalty_result["tx_hash"],
ip_id=royalty_result["ip_id"],
token_id=royalty_result["token_id"],
royalty_vault=royalty_result["royalty_vault"],
)

if deriv_data:
deriv_result = self.mint_and_register_ip_and_make_derivative(
spg_nft_contract=nft.spg_nft_contract,
deriv_data=deriv_data,
ip_metadata=ip_metadata,
recipient=nft.recipient,
allow_duplicates=nft.allow_duplicates,
tx_options=tx_options,
)
return RegisterDerivativeIpAssetResponse(
tx_hash=deriv_result["tx_hash"],
ip_id=deriv_result["ip_id"],
token_id=deriv_result["token_id"],
)

# Use license_token_ids
token_result = (
self.mint_and_register_ip_and_make_derivative_with_license_tokens(
spg_nft_contract=nft.spg_nft_contract,
license_token_ids=cast(list[int], license_token_ids),
max_rts=max_rts,
recipient=nft.recipient,
allow_duplicates=nft.allow_duplicates,
ip_metadata=ip_metadata,
tx_options=tx_options,
)
)
return RegisterDerivativeIpAssetResponse(
tx_hash=token_result["tx_hash"],
ip_id=token_result["ip_id"],
token_id=token_result["token_id"],
)

def _validate_derivative_data(self, derivative_data: dict) -> dict:
"""
Validates the derivative data and returns processed internal data.
Expand Down
20 changes: 20 additions & 0 deletions src/story_protocol_python_sdk/types/resource/IPAsset.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,23 @@ class RegisterIpAssetResponse(TypedDict, total=False):
license_terms_ids: list[int]
royalty_vault: Address
distribute_royalty_tokens_tx_hash: HexStr


class RegisterDerivativeIpAssetResponse(TypedDict, total=False):
"""
Response structure for unified derivative IP asset registration.
Fields vary based on the registration method used.

Attributes:
tx_hash: The transaction hash of the registration transaction.
ip_id: The IP ID of the registered IP asset.
token_id: The token ID of the registered IP asset.
royalty_vault: [Optional] The royalty vault address of the registered IP asset.
distribute_royalty_tokens_tx_hash: [Optional] The transaction hash of the distribute royalty tokens transaction.
"""

tx_hash: HexStr
ip_id: Address
token_id: int
royalty_vault: Address
distribute_royalty_tokens_tx_hash: HexStr
4 changes: 3 additions & 1 deletion src/story_protocol_python_sdk/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
ZERO_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"
ZERO_FUNC = "0x00000000"
DEFAULT_FUNCTION_SELECTOR = "0x00000000"
MAX_ROYALTY_TOKEN = 100000000
MAX_ROYALTY_TOKEN = 100_000_000
ROYALTY_POLICY_LAP_ADDRESS = "0xBe54FB168b3c982b7AaE60dB6CF75Bd8447b390E"
ROYALTY_POLICY_LRP_ADDRESS = "0x9156E603C949481883B1D3355C6F1132D191FC41"
WIP_TOKEN_ADDRESS = "0x1514000000000000000000000000000000000000"
# Default deadline for signature in seconds
DEADLINE = 1000
Loading
Loading