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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Story Protocol SDK
# Story SDK

Welcome to the documents for Story Protocol Python SDK. The Python SDK provides the APIs for developers to build applications with Story Protocol. By using the SDK, developers can create the resources like IP assets and perform actions to interact with the resource.
Welcome to the documents for Story Python SDK. The Python SDK provides the APIs for developers to build applications with Story. By using the SDK, developers can create the resources like IP assets and perform actions to interact with the resource.

## How to use Story Protocol SDK in Your Project

Expand Down Expand Up @@ -48,14 +48,14 @@ The preceding code created the `account` object for creating the SDK client.

To set up the SDK client, import `StoryClient` from `story_protocol_python_sdk`. Write the following code, utilizing the `account` we created previously.

> :information-source: Make sure to have RPC_PROVIDER_URL for your desired chain set up in your .env file. We recommend using the Sepolia network with `RPC_PROVIDER_URL=https://rpc.ankr.com/eth_sepolia`.
> :information-source: Make sure to have RPC_PROVIDER_URL for your desired chain set up in your .env file. We recommend using the public default one with `RPC_PROVIDER_URL=https://aeneid.storyrpc.io`.

```Python main.py
from story_protocol_python_sdk import StoryClient

# Create StoryClient instance
odyssey_chain_id = 1516
story_client = StoryClient(web3, account, odyssey_chain_id)
aeneid_chain_id = 1315
story_client = StoryClient(web3, account, aeneid_chain_id)
```

## Running test cases
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='story_protocol_python_sdk',
version='0.3.5',
version='0.3.7',
packages=find_packages(where='src', exclude=["tests"]),
package_dir={'': 'src'},
install_requires=[
Expand All @@ -18,7 +18,7 @@
license='MIT',
author='Andrew Chung',
author_email='andrew@storyprotocol.xyz',
description='A Python SDK for interacting with the Story Protocol.',
description='A Python SDK for interacting with Story.',
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
classifiers=[
Expand Down
12 changes: 6 additions & 6 deletions src/story_protocol_python_sdk/resources/IPAsset.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ def register(
:param nft_contract str: The address of the NFT.
:param token_id int: The token identifier of the NFT.
:param ip_metadata dict: [Optional] Metadata for the IP.
:param ip_metadata_URI str: [Optional] Metadata URI for the IP.
:param ip_metadata_uri str: [Optional] Metadata URI for the IP.
:param ip_metadata_hash str: [Optional] Metadata hash for the IP.
:param nft_metadata_URI str: [Optional] Metadata URI for the NFT.
:param nft_metadata_uri str: [Optional] Metadata URI for the NFT.
:param nft_metadata_hash str: [Optional] Metadata hash for the NFT.
:param deadline int: [Optional] Signature deadline in milliseconds.
:param tx_options dict: [Optional] Transaction options.
Expand Down Expand Up @@ -96,10 +96,10 @@ def register(

if ip_metadata:
req_object['ipMetadata'].update({
'ipMetadataURI': ip_metadata.get('ipMetadataURI', ""),
'ipMetadataHash': ip_metadata.get('ipMetadataHash', ZERO_HASH),
'nftMetadataURI': ip_metadata.get('nftMetadataURI', ""),
'nftMetadataHash': ip_metadata.get('nftMetadataHash', ZERO_HASH),
'ipMetadataURI': ip_metadata.get('ip_metadata_uri', ""),
'ipMetadataHash': ip_metadata.get('ip_metadata_hash', ZERO_HASH),
'nftMetadataURI': ip_metadata.get('nft_metadata_uri', ""),
'nftMetadataHash': ip_metadata.get('nft_metadata_hash', ZERO_HASH),
})

calculated_deadline = self.sign_util.get_deadline(deadline=deadline)
Expand Down
2 changes: 1 addition & 1 deletion src/story_protocol_python_sdk/story_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, web3, account, chain_id: int):
if not web3 or not account:
raise ValueError("web3 and account must be provided")

if chain_id != 1315:
if chain_id not in [1315, 1514]:
raise ValueError("only support story devnet")

self.web3 = web3
Expand Down
58 changes: 21 additions & 37 deletions tests/demo/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
from web3 import Web3

from story_protocol_python_sdk import StoryClient
from demo_utils import get_token_id, MockERC721, MockERC20, mint_tokens
from demo_utils import get_token_id, MockERC721, MockERC20, mint_tokens, ROYALTY_POLICY, ROYALTY_MODULE, PIL_LICENSE_TEMPLATE

def main():
# 1. Set up your Story Config
load_dotenv()
private_key = os.getenv('WALLET_PRIVATE_KEY')
rpc_url = os.getenv('RPC_PROVIDER_URL')

if not private_key:
raise ValueError("WALLET_PRIVATE_KEY environment variable is not set")
if not rpc_url:
raise ValueError("RPC_PROVIDER_URL environment variable is not set")

# Initialize Web3
web3 = Web3(Web3.HTTPProvider(rpc_url))
if not web3.is_connected():
Expand All @@ -20,26 +25,22 @@ def main():
account = web3.eth.account.from_key(private_key)

# Create StoryClient instance
story_client = StoryClient(web3, account, 11155111)
story_client = StoryClient(web3, account, 1315)

# 2. Register an IP Asset
token_id = get_token_id(MockERC721, story_client.web3, story_client.account)

registered_ip_asset_response = story_client.IPAsset.register(
token_contract=MockERC721,
nft_contract=MockERC721,
token_id=token_id
)
print(f"Root IPA created at transaction hash {registered_ip_asset_response['txHash']}, IPA ID: {registered_ip_asset_response['ipId']}")

# 3. Register PIL Terms
commercial_use_params = {
'currency': MockERC20,
'minting_fee': 2,
'royalty_policy': "0xAAbaf349C7a2A84564F9CC4Ac130B3f19A718E86"
}
register_pil_terms_response = story_client.License.registerCommercialUsePIL(
minting_fee=commercial_use_params['minting_fee'],
currency=commercial_use_params['currency'],
royalty_policy=commercial_use_params['royalty_policy']
default_minting_fee=1,
currency=MockERC20,
royalty_policy=ROYALTY_POLICY
)
if 'txHash' in register_pil_terms_response:
print(f"PIL Terms registered at transaction hash {register_pil_terms_response['txHash']}, License Terms ID: {register_pil_terms_response['licenseTermsId']}")
Expand All @@ -50,59 +51,42 @@ def main():
try:
attach_license_terms_response = story_client.License.attachLicenseTerms(
ip_id=registered_ip_asset_response['ipId'],
license_template="0x260B6CB6284c89dbE660c0004233f7bB99B5edE7",
license_template=PIL_LICENSE_TEMPLATE,
license_terms_id=register_pil_terms_response['licenseTermsId']
)
print(f"Attached License Terms to IP at transaction hash {attach_license_terms_response['txHash']}")
except Exception as e:
print(f"License Terms ID {register_pil_terms_response['licenseTermsId']} already attached to this IPA.")

#Before you mint make sure you have enough ERC20 tokens according to the minting fee above
token_ids = mint_tokens(MockERC20, web3, account, account.address, 2)
token_ids = mint_tokens(MockERC20, web3, account, account.address, 10000)

# 5. Mint License
mint_license_response = story_client.License.mintLicenseTokens(
licensor_ip_id=registered_ip_asset_response['ipId'],
license_template="0x260B6CB6284c89dbE660c0004233f7bB99B5edE7",
license_template=PIL_LICENSE_TEMPLATE,
license_terms_id=register_pil_terms_response['licenseTermsId'],
amount=1,
receiver=account.address
receiver=account.address,
max_minting_fee=1,
max_revenue_share=0
)
print(f"License Token minted at transaction hash {mint_license_response['txHash']}, License Token IDs: {mint_license_response['licenseTokenIds']}")

# 6. Mint derivative IP Asset using your license
derivative_token_id = get_token_id(MockERC721, story_client.web3, story_client.account)
registered_ip_asset_derivative_response = story_client.IPAsset.register(
token_contract=MockERC721,
nft_contract=MockERC721,
token_id=derivative_token_id
)
print(f"Derivative IPA created at transaction hash {registered_ip_asset_derivative_response['txHash']}, IPA ID: {registered_ip_asset_derivative_response['ipId']}")

link_derivative_response = story_client.IPAsset.registerDerivativeWithLicenseTokens(
child_ip_id=registered_ip_asset_derivative_response['ipId'],
license_token_ids=mint_license_response['licenseTokenIds']
license_token_ids=mint_license_response['licenseTokenIds'],
max_rts=5 * 10 ** 6
)
print(f"Derivative IPA linked to parent at transaction hash {link_derivative_response['txHash']}")

# 7. Collect Royalty Tokens
collect_royalty_tokens_response = story_client.Royalty.collectRoyaltyTokens(
parent_ip_id=registered_ip_asset_response['ipId'],
child_ip_id=registered_ip_asset_derivative_response['ipId']
)
print(f"Collected royalty token {collect_royalty_tokens_response['royaltyTokensCollected']} at transaction hash {collect_royalty_tokens_response['txHash']}")

# 8. Claim Revenue
snapshot_response = story_client.Royalty.snapshot(
child_ip_id=registered_ip_asset_derivative_response['ipId']
)
print(f"Took a snapshot with ID {snapshot_response['snapshotId']} at transaction hash {snapshot_response['txHash']}")

claim_revenue_response = story_client.Royalty.claimRevenue(
snapshot_ids=[snapshot_response['snapshotId']],
child_ip_id=registered_ip_asset_derivative_response['ipId'],
token=MockERC20
)
print(f"Claimed revenue token {claim_revenue_response['claimableToken']} at transaction hash {claim_revenue_response['txHash']}")

if __name__ == "__main__":
main()
42 changes: 25 additions & 17 deletions tests/demo/demo_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Mock ERC721 contract address
MockERC721 = "0x7ee32b8B515dEE0Ba2F25f612A04a731eEc24F49"
MockERC721 = "0xa1119092ea911202E0a65B743a13AE28C5CF2f21"

# Mock ERC20 contract address (same as used in TypeScript tests)
MockERC20 = "0xB132A6B7AE652c974EE1557A3521D53d18F6739f"
MockERC20 = "0xF2104833d386a2734a4eB3B8ad6FC6812F29E38E"

ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
ROYALTY_POLICY="0xBe54FB168b3c982b7AaE60dB6CF75Bd8447b390E" #Royalty Policy LAP
ROYALTY_MODULE="0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086"
PIL_LICENSE_TEMPLATE="0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316"

def get_token_id(nft_contract, web3, account):
contract_abi = [
Expand All @@ -15,23 +20,26 @@ def get_token_id(nft_contract, web3, account):
}
]

# Fetch the current average gas price from the node plus 10%
current_gas_price = int(web3.eth.gas_price * 1.1)

contract = web3.eth.contract(address=nft_contract, abi=contract_abi)
transaction = contract.functions.mint(account.address).build_transaction({
'from': account.address,
'nonce': web3.eth.get_transaction_count(account.address),
'gas': 2000000,
'gasPrice': current_gas_price
})
signed_txn = account.sign_transaction(transaction)
tx_hash = web3.eth.send_raw_transaction(signed_txn.raw_transaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)

try:
transaction = contract.functions.mint(account.address).build_transaction({
'from': account.address,
'nonce': web3.eth.get_transaction_count(account.address),
'gas': 2000000
})

signed_txn = account.sign_transaction(transaction)
tx_hash = web3.eth.send_raw_transaction(signed_txn.raw_transaction)
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)

logs = tx_receipt['logs']
if logs[0]['topics'][3]:
return int(logs[0]['topics'][3].hex(), 16)
logs = tx_receipt['logs']
if len(logs) > 0 and len(logs[0]['topics']) > 3:
return int(logs[0]['topics'][3].hex(), 16)
raise ValueError(f"No token ID in logs: {tx_receipt}")

except Exception as e:
raise e

def mint_tokens(erc20_contract_address, web3, account, to_address, amount):
contract_abi = [
Expand Down
16 changes: 13 additions & 3 deletions tests/integration/test_integration_ip_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ def test_register_ip_asset(story_client, child_ip_id):
def test_register_ip_asset_with_metadata(story_client):
token_id = get_token_id(MockERC721, story_client.web3, story_client.account)
metadata = {
'metadataURI': "test-uri",
'metadataHash': web3.to_hex(web3.keccak(text="test-metadata-hash")),
'nftMetadataHash': web3.to_hex(web3.keccak(text="test-nft-metadata-hash"))
'ip_metadata_uri': "test-uri",
'ip_metadata_hash': web3.to_hex(web3.keccak(text="test-metadata-hash")),
'nft_metadata_uri': "test-nft-uri",
'nft_metadata_hash': web3.to_hex(web3.keccak(text="test-nft-metadata-hash"))
}

response = story_client.IPAsset.register(
Expand Down Expand Up @@ -143,6 +144,14 @@ def nft_collection(story_client):
return txData['nftContract']

def test_mint_register_attach_terms(story_client, nft_collection):

metadata = {
'ip_metadata_uri': "test-uri",
'ip_metadata_hash': web3.to_hex(web3.keccak(text="test-metadata-hash")),
'nft_metadata_uri': "test-nft-uri",
'nft_metadata_hash': web3.to_hex(web3.keccak(text="test-nft-metadata-hash"))
}

response = story_client.IPAsset.mintAndRegisterIpAssetWithPilTerms(
spg_nft_contract=nft_collection,
terms=[{
Expand Down Expand Up @@ -176,6 +185,7 @@ def test_mint_register_attach_terms(story_client, nft_collection):
'expect_group_reward_pool': ZERO_ADDRESS
}
}],
ip_metadata=metadata
)

assert 'txHash' in response
Expand Down