Skip to content

Commit f2a3f7a

Browse files
authored
Feat: add the get_licensing_config method (#137)
* feat: add LicensingConfig and LicensingConfigData classes for licensing configuration management * feat: introduce ValidatedLicensingConfig class and update validation method to return validated configuration * feat: enhance LicensingConfigData validation with module registry checks and add unit tests for licensing hook registration * feat: add getLicensingConfig method to LicenseRegistryClient and update License class to utilize LicensingConfig for improved licensing configuration management * refactor: reorganize licensing configuration tests into TestLicensingConfig class and update assertions for improved clarity and structure * refactor: update License class methods to return consistent dict types and improve logging checks; enhance test structure by utilizing existing fixtures and updating assertions * refactor: replace direct address validation in License class with a dedicated validate_address function; enhance type hints in test cases for improved clarity * refactor: streamline transaction hash handling in License class methods for consistency; update licensing hook documentation for clarity
1 parent c0f01cd commit f2a3f7a

File tree

9 files changed

+1267
-553
lines changed

9 files changed

+1267
-553
lines changed

src/story_protocol_python_sdk/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
)
2929
from .utils.derivative_data import DerivativeDataInput
3030
from .utils.ip_metadata import IPMetadataInput
31+
from .utils.licensing_config_data import LicensingConfig
3132

3233
__all__ = [
3334
"StoryClient",
@@ -45,6 +46,7 @@
4546
"ClaimRewardsResponse",
4647
"ClaimReward",
4748
"CollectRoyaltiesResponse",
49+
"LicensingConfig",
4850
"RegisterPILTermsAndAttachResponse",
4951
# Constants
5052
"ZERO_ADDRESS",

src/story_protocol_python_sdk/abi/LicenseRegistry/LicenseRegistry_client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ def __init__(self, web3: Web3):
3939
def exists(self, licenseTemplate, licenseTermsId):
4040
return self.contract.functions.exists(licenseTemplate, licenseTermsId).call()
4141

42+
def getLicensingConfig(self, ipId, licenseTemplate, licenseTermsId):
43+
return self.contract.functions.getLicensingConfig(
44+
ipId, licenseTemplate, licenseTermsId
45+
).call()
46+
4247
def getRoyaltyPercent(self, ipId, licenseTemplate, licenseTermsId):
4348
return self.contract.functions.getRoyaltyPercent(
4449
ipId, licenseTemplate, licenseTermsId

src/story_protocol_python_sdk/resources/License.py

Lines changed: 64 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
# src/story_protcol_python_sdk/resources/License.py
2-
3-
from ens.ens import HexStr
1+
from ens.ens import Address, HexStr
42
from web3 import Web3
53

64
from story_protocol_python_sdk.abi.IPAssetRegistry.IPAssetRegistry_client import (
@@ -21,8 +19,15 @@
2119
from story_protocol_python_sdk.types.common import RevShareType
2220
from story_protocol_python_sdk.utils.constants import ZERO_ADDRESS
2321
from story_protocol_python_sdk.utils.license_terms import LicenseTerms
22+
from story_protocol_python_sdk.utils.licensing_config_data import (
23+
LicensingConfig,
24+
LicensingConfigData,
25+
)
2426
from story_protocol_python_sdk.utils.transaction_utils import build_and_send_transaction
25-
from story_protocol_python_sdk.utils.validation import get_revenue_share
27+
from story_protocol_python_sdk.utils.validation import (
28+
get_revenue_share,
29+
validate_address,
30+
)
2631

2732

2833
class License:
@@ -183,7 +188,7 @@ def register_commercial_use_pil(
183188
currency: str,
184189
royalty_policy: str | None = None,
185190
tx_options: dict | None = None,
186-
) -> dict | None:
191+
) -> dict:
187192
"""
188193
Convenient function to register a PIL commercial use license to the registry.
189194
@@ -214,14 +219,14 @@ def register_commercial_use_pil(
214219
complete_license_terms,
215220
tx_options=tx_options,
216221
)
217-
218-
if not response["tx_receipt"].logs:
219-
return None
222+
tx_hash = response["tx_hash"]
223+
if not response["tx_receipt"]["logs"]:
224+
return {"tx_hash": tx_hash}
220225

221226
target_logs = self._parse_tx_license_terms_registered_event(
222227
response["tx_receipt"]
223228
)
224-
return {"tx_hash": response["tx_hash"], "license_terms_id": target_logs}
229+
return {"tx_hash": tx_hash, "license_terms_id": target_logs}
225230

226231
except Exception as e:
227232
raise e
@@ -233,7 +238,7 @@ def register_commercial_remix_pil(
233238
commercial_rev_share: int,
234239
royalty_policy: str,
235240
tx_options: dict | None = None,
236-
) -> dict | None:
241+
) -> dict:
237242
"""
238243
Convenient function to register a PIL commercial remix license to the registry.
239244
@@ -267,13 +272,14 @@ def register_commercial_remix_pil(
267272
tx_options=tx_options,
268273
)
269274

270-
if not response["tx_receipt"].logs:
271-
return None
275+
tx_hash = response["tx_hash"]
276+
if not response["tx_receipt"]["logs"]:
277+
return {"tx_hash": tx_hash}
272278

273279
target_logs = self._parse_tx_license_terms_registered_event(
274280
response["tx_receipt"]
275281
)
276-
return {"tx_hash": response["tx_hash"], "license_terms_id": target_logs}
282+
return {"tx_hash": tx_hash, "license_terms_id": target_logs}
277283

278284
except Exception as e:
279285
raise e
@@ -375,12 +381,8 @@ def mint_license_tokens(
375381
:return dict: A dictionary with the transaction hash and the license token IDs.
376382
"""
377383
try:
378-
if not self.web3.is_address(license_template):
379-
raise ValueError(f'Address "{license_template}" is invalid.')
380-
381-
if not self.web3.is_address(receiver):
382-
raise ValueError(f'Address "{receiver}" is invalid.')
383-
384+
validate_address(license_template)
385+
validate_address(receiver)
384386
is_registered = self.ip_asset_registry_client.isRegistered(licensor_ip_id)
385387
if not is_registered:
386388
raise ValueError(
@@ -522,7 +524,7 @@ def set_licensing_config(
522524
self,
523525
ip_id: str,
524526
license_terms_id: int,
525-
licensing_config: dict,
527+
licensing_config: LicensingConfig,
526528
license_template: str | None = None,
527529
tx_options: dict | None = None,
528530
) -> dict:
@@ -531,86 +533,36 @@ def set_licensing_config(
531533
532534
:param ip_id str: The address of the IP for which the configuration is being set.
533535
:param license_terms_id int: The ID of the license terms within the license template.
534-
:param licensing_config dict: The licensing configuration for the license.
535-
:param isSet bool: Whether the configuration is set or not.
536-
:param mintingFee int: The minting fee to be paid when minting license tokens.
537-
:param hookData str: The data to be used by the licensing hook.
538-
:param licensingHook str: The hook contract address for the licensing module, or address(0) if none.
539-
:param commercialRevShare int: The commercial revenue share percentage.
540-
:param disabled bool: Whether the license is disabled or not.
541-
:param expectMinimumGroupRewardShare int: The minimum percentage of the group's reward share (0-100%, as 100 * 10^6).
542-
:param expectGroupRewardPool str: The address of the expected group reward pool.
536+
:param licensing_config `LicensingConfig`: The licensing configuration for the license.
543537
:param license_template str: [Optional] The address of the license template used. If not specified, config applies to all licenses.
544538
:param tx_options dict: [Optional] Transaction options.
545539
:return dict: A dictionary containing the transaction hash and success status.
546540
"""
547541
try:
548-
# Input validation
549-
required_params = {
550-
"isSet",
551-
"mintingFee",
552-
"hookData",
553-
"licensingHook",
554-
"commercialRevShare",
555-
"disabled",
556-
"expectMinimumGroupRewardShare",
557-
"expectGroupRewardPool",
558-
}
559-
560-
# Check for missing parameters
561-
missing_params = required_params - set(licensing_config.keys())
562-
if missing_params:
563-
raise ValueError(
564-
f"Missing required licensing_config parameters: {', '.join(missing_params)}. "
565-
f"All parameters must be explicitly provided."
566-
)
567-
568-
licensing_config["commercialRevShare"] = (
569-
self.license_terms_util.get_revenue_share(
570-
licensing_config["commercialRevShare"]
571-
)
542+
validated_licensing_config = LicensingConfigData.validate_license_config(
543+
self.module_registry_client, licensing_config
572544
)
573-
574-
if licensing_config["mintingFee"] < 0:
575-
raise ValueError("The minting fee must be greater than 0.")
576-
577-
if not license_template:
578-
license_template = ZERO_ADDRESS
545+
if license_template is None:
546+
license_template = self.license_template_client.contract.address
547+
else:
548+
validate_address(license_template)
579549

580550
if (
581551
license_template == ZERO_ADDRESS
582-
and licensing_config["commercialRevShare"] != 0
552+
and validated_licensing_config["commercialRevShare"] != 0
583553
):
584554
raise ValueError(
585555
"The license template cannot be zero address if commercial revenue share is not zero."
586556
)
587557

588-
# Convert addresses to checksum format
589-
ip_id = self.web3.to_checksum_address(ip_id)
590-
if license_template:
591-
license_template = self.web3.to_checksum_address(license_template)
592-
licensing_config["licensingHook"] = self.web3.to_checksum_address(
593-
licensing_config["licensingHook"]
594-
)
595-
licensing_config["expectGroupRewardPool"] = self.web3.to_checksum_address(
596-
licensing_config["expectGroupRewardPool"]
597-
)
598-
599558
# Check if IP is registered
600-
if not self.ip_asset_registry_client.isRegistered(ip_id):
559+
if not self.ip_asset_registry_client.isRegistered(validate_address(ip_id)):
601560
raise ValueError(f"The licensor IP with id {ip_id} is not registered.")
602561

603562
# Check if license terms exist
604563
if not self.license_template_client.exists(license_terms_id):
605564
raise ValueError(f"License terms id {license_terms_id} does not exist.")
606565

607-
# Check if licensing hook is registered if provided
608-
if licensing_config["licensingHook"] != ZERO_ADDRESS:
609-
if not self.module_registry_client.isRegistered(
610-
licensing_config["licensingHook"]
611-
):
612-
raise ValueError("The licensing hook is not registered.")
613-
614566
if license_template == ZERO_ADDRESS and license_terms_id != 0:
615567
raise ValueError(
616568
"The license template is zero address but license terms id is not zero."
@@ -623,7 +575,7 @@ def set_licensing_config(
623575
ip_id,
624576
license_template,
625577
license_terms_id,
626-
licensing_config,
578+
validated_licensing_config,
627579
tx_options=tx_options,
628580
)
629581

@@ -634,3 +586,35 @@ def set_licensing_config(
634586

635587
except Exception as e:
636588
raise ValueError(f"Failed to set licensing config: {str(e)}")
589+
590+
def get_licensing_config(
591+
self,
592+
ip_id: Address,
593+
license_terms_id: int,
594+
license_template: Address | None = None,
595+
) -> LicensingConfig:
596+
"""
597+
Gets the licensing configuration for a specific license terms of an IP.
598+
599+
:param ip_id Address: The address of the IP for which the configuration is being retrieved.
600+
:param license_terms_id int: The ID of the license terms within the license template.
601+
:param license_template Address: [Optional] The address of the license template.
602+
Defaults to visit https://docs.story.foundation/docs/programmable-ip-license for more information if not provided.
603+
:return LicensingConfig: A dictionary containing the licensing configuration.
604+
"""
605+
try:
606+
validate_address(ip_id)
607+
608+
if license_template is None:
609+
license_template = self.license_template_client.contract.address
610+
else:
611+
validate_address(license_template)
612+
613+
licensing_config = self.license_registry_client.getLicensingConfig(
614+
ip_id, license_template, license_terms_id
615+
)
616+
617+
return LicensingConfigData.from_tuple(licensing_config)
618+
619+
except Exception as e:
620+
raise ValueError(f"Failed to get licensing config: {str(e)}")

src/story_protocol_python_sdk/scripts/config.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,12 @@
191191
{
192192
"contract_name": "LicenseRegistry",
193193
"contract_address": "0x529a750E02d8E2f15649c13D69a465286a780e24",
194-
"functions": ["exists", "hasIpAttachedLicenseTerms", "getRoyaltyPercent"]
194+
"functions": [
195+
"exists",
196+
"hasIpAttachedLicenseTerms",
197+
"getRoyaltyPercent",
198+
"getLicensingConfig"
199+
]
195200
},
196201
{
197202
"contract_name": "RoyaltyPolicyLRP",

0 commit comments

Comments
 (0)