From b6fe4562b4dc23e858cd86ea2ddef9e8b43159ee Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 20 Nov 2024 11:22:09 +0200 Subject: [PATCH] add missing token management methods --- multiversx_sdk/__init__.py | 5 +- .../core/transactions_factory_config.py | 8 + multiversx_sdk/network_providers/__init__.py | 3 +- .../token_management_controller.py | 132 +++++++++++++ .../token_management_transactions_factory.py | 173 +++++++++++++++++- ...en_management_transactions_factory_test.py | 117 +++++++++++- 6 files changed, 433 insertions(+), 5 deletions(-) diff --git a/multiversx_sdk/__init__.py b/multiversx_sdk/__init__.py index 77ecfd59..9b3ce2b6 100644 --- a/multiversx_sdk/__init__.py +++ b/multiversx_sdk/__init__.py @@ -17,7 +17,8 @@ DelegationTransactionsOutcomeParser) from multiversx_sdk.entrypoints import (DevnetEntrypoint, MainnetEntrypoint, NetworkEntrypoint, TestnetEntrypoint) -from multiversx_sdk.network_providers import (AccountOnNetwork, AccountStorage, +from multiversx_sdk.network_providers import (AccountAwaiter, AccountOnNetwork, + AccountStorage, AccountStorageEntry, ApiNetworkProvider, AwaitingOptions, @@ -79,5 +80,5 @@ "BurnQuantityOutcome", "TransactionOnNetwork", "TransactionStatus", "ParsedSmartContractCallOutcome", "AccountOnNetwork", "AccountStorage", "AccountStorageEntry", "AwaitingOptions", "BlockCoordinates", "BlockOnNetwork", "FungibleTokenMetadata", "GetBlockArguments", "NetworkConfig", "NetworkStatus", - "TokenAmountOnNetwork", "TokensCollectionMetadata", "TransactionCostResponse" + "TokenAmountOnNetwork", "TokensCollectionMetadata", "TransactionCostResponse", "AccountAwaiter" ] diff --git a/multiversx_sdk/core/transactions_factory_config.py b/multiversx_sdk/core/transactions_factory_config.py index 211f7626..fbc5e28a 100644 --- a/multiversx_sdk/core/transactions_factory_config.py +++ b/multiversx_sdk/core/transactions_factory_config.py @@ -35,6 +35,14 @@ class TransactionsFactoryConfig: gas_limit_update_token_id: int = 60_000_000 gas_limit_register_dynamic: int = 60_000_000 issue_cost: int = 50_000_000_000_000_000 + gas_limit_transfer_ownership: int = 60_000_000 + gas_limit_freeze_single_nft: int = 60_000_000 + gas_limit_unfreeze_single_nft: int = 60_000_000 + gas_limit_change_sft_to_meta_esdt: int = 60_000_000 + gas_limit_transfer_nft_create_role: int = 60_000_000 + gas_limit_stop_nft_create: int = 60_000_000 + gas_limit_wipe_single_nft: int = 60_000_000 + gas_limit_esdt_nft_add_uri: int = 10_000_000 esdt_contract_address: Address = field(default_factory=lambda: Address.new_from_bech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u")) diff --git a/multiversx_sdk/network_providers/__init__.py b/multiversx_sdk/network_providers/__init__.py index 7cb8ce5e..840a75c8 100644 --- a/multiversx_sdk/network_providers/__init__.py +++ b/multiversx_sdk/network_providers/__init__.py @@ -1,3 +1,4 @@ +from multiversx_sdk.network_providers.account_awaiter import AccountAwaiter from multiversx_sdk.network_providers.api_network_provider import \ ApiNetworkProvider from multiversx_sdk.network_providers.config import NetworkProviderConfig @@ -21,5 +22,5 @@ "AccountOnNetwork", "AccountStorage", "AccountStorageEntry", "AwaitingOptions", "BlockCoordinates", "BlockOnNetwork", "FungibleTokenMetadata", "GetBlockArguments", "NetworkConfig", "NetworkStatus", "TokenAmountOnNetwork", - "TokensCollectionMetadata", "TransactionCostResponse" + "TokensCollectionMetadata", "TransactionCostResponse", "AccountAwaiter" ] diff --git a/multiversx_sdk/token_management/token_management_controller.py b/multiversx_sdk/token_management/token_management_controller.py index 447c3e6a..43c96239 100644 --- a/multiversx_sdk/token_management/token_management_controller.py +++ b/multiversx_sdk/token_management/token_management_controller.py @@ -609,3 +609,135 @@ def parse_burn_quantity(self, transaction_on_network: TransactionOnNetwork) -> l def await_completed_burn_quantity(self, transaction_hash: Union[str, bytes]) -> list[BurnQuantityOutcome]: transaction = self.network_provider.await_transaction_completed(transaction_hash) return self.parse_burn_quantity(transaction) + + def create_transaction_for_transferring_ownership(self, + sender: IAccount, + nonce: int, + token_identifier: str, + new_owner: Address) -> Transaction: + transaction = self.factory.create_transaction_for_transferring_ownership( + sender=sender.address, + token_identifier=token_identifier, + new_owner=new_owner + ) + + transaction.nonce = nonce + transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) + + return transaction + + def create_transaction_for_freezing_single_nft(self, + sender: IAccount, + nonce: int, + token_identifier: str, + token_nonce: int, + user: Address) -> Transaction: + transaction = self.factory.create_transaction_for_freezing_single_nft( + sender=sender.address, + token_identifier=token_identifier, + token_nonce=token_nonce, + user=user + ) + + transaction.nonce = nonce + transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) + + return transaction + + def create_transaction_for_unfreezing_single_nft(self, + sender: IAccount, + nonce: int, + token_identifier: str, + token_nonce: int, + user: Address) -> Transaction: + transaction = self.factory.create_transaction_for_unfreezing_single_nft( + sender=sender.address, + token_identifier=token_identifier, + token_nonce=token_nonce, + user=user + ) + + transaction.nonce = nonce + transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) + + return transaction + + def create_transaction_for_changing_sft_to_meta_esdt(self, + sender: IAccount, + nonce: int, + collection: str, + num_decimals: int) -> Transaction: + transaction = self.factory.create_transaction_for_changing_sft_to_meta_esdt( + sender=sender.address, + collection=collection, + num_decimals=num_decimals + ) + + transaction.nonce = nonce + transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) + + return transaction + + def create_transaction_for_transferring_nft_create_role(self, + sender: IAccount, + nonce: int, + token_identifier: str, + user: Address) -> Transaction: + transaction = self.factory.create_transaction_for_transferring_nft_create_role( + sender=sender.address, + token_identifier=token_identifier, + user=user + ) + + transaction.nonce = nonce + transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) + + return transaction + + def create_transaction_for_stopping_nft_creation(self, + sender: IAccount, + nonce: int, + token_identifier: str) -> Transaction: + transaction = self.factory.create_transaction_for_stopping_nft_creation( + sender=sender.address, + token_identifier=token_identifier + ) + + transaction.nonce = nonce + transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) + + return transaction + + def create_transaction_for_wiping_single_nft(self, + sender: IAccount, + nonce: int, + token_identifier: str, + token_nonce: int, + user: Address) -> Transaction: + transaction = self.factory.create_transaction_for_wiping_single_nft( + sender=sender.address, + token_identifier=token_identifier, + token_nonce=token_nonce, + user=user + ) + + transaction.nonce = nonce + transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) + + return transaction + + def create_transction_for_adding_uris(self, + sender: IAccount, + nonce: int, + token_identifier: str, + uris: list[str]) -> Transaction: + transaction = self.factory.create_transction_for_adding_uris( + sender=sender.address, + token_identifier=token_identifier, + uris=uris + ) + + transaction.nonce = nonce + transaction.signature = sender.sign(self.tx_computer.compute_bytes_for_signing(transaction)) + + return transaction diff --git a/multiversx_sdk/token_management/token_management_transactions_factory.py b/multiversx_sdk/token_management/token_management_transactions_factory.py index 131c84c0..1986a30b 100644 --- a/multiversx_sdk/token_management/token_management_transactions_factory.py +++ b/multiversx_sdk/token_management/token_management_transactions_factory.py @@ -608,6 +608,7 @@ def create_transaction_for_freezing( user: Address, token_identifier: str ) -> Transaction: + """Can be used for FungibleESDT""" parts = [ "freeze", self.serializer.serialize([StringValue(token_identifier)]), @@ -630,6 +631,7 @@ def create_transaction_for_unfreezing( user: Address, token_identifier: str ) -> Transaction: + """Can be used for FungibleESDT""" parts = [ "unFreeze", self.serializer.serialize([StringValue(token_identifier)]), @@ -850,7 +852,7 @@ def create_transaction_for_modifying_creator(self, sender: Address, token_identifier: str, token_nonce: int) -> Transaction: - parts: list[str] = [ + parts = [ "ESDTModifyCreator", self.serializer.serialize([StringValue(token_identifier)]), self.serializer.serialize([BigUIntValue(token_nonce)]) @@ -1033,6 +1035,175 @@ def create_transaction_for_registering_dynamic_and_setting_roles(self, data_parts=parts ).build() + def create_transaction_for_transferring_ownership(self, + sender: Address, + token_identifier: str, + new_owner: Address) -> Transaction: + parts = [ + "transferOwnership", + self.serializer.serialize([StringValue(token_identifier)]), + new_owner.to_hex() + ] + + return TransactionBuilder( + config=self._config, + sender=sender, + receiver=self._config.esdt_contract_address, + amount=0, + gas_limit=self._config.gas_limit_transfer_ownership, + add_data_movement_gas=True, + data_parts=parts + ).build() + + def create_transaction_for_freezing_single_nft(self, + sender: Address, + token_identifier: str, + token_nonce: int, + user: Address) -> Transaction: + parts = [ + "freezeSingleNFT", + self.serializer.serialize([StringValue(token_identifier)]), + self.serializer.serialize([BigUIntValue(token_nonce)]), + user.to_hex() + ] + + return TransactionBuilder( + config=self._config, + sender=sender, + receiver=self._config.esdt_contract_address, + amount=0, + gas_limit=self._config.gas_limit_freeze_single_nft, + add_data_movement_gas=True, + data_parts=parts + ).build() + + def create_transaction_for_unfreezing_single_nft(self, + sender: Address, + token_identifier: str, + token_nonce: int, + user: Address) -> Transaction: + parts = [ + "unFreezeSingleNFT", + self.serializer.serialize([StringValue(token_identifier)]), + self.serializer.serialize([BigUIntValue(token_nonce)]), + user.to_hex() + ] + + return TransactionBuilder( + config=self._config, + sender=sender, + receiver=self._config.esdt_contract_address, + amount=0, + gas_limit=self._config.gas_limit_unfreeze_single_nft, + add_data_movement_gas=True, + data_parts=parts + ).build() + + def create_transaction_for_changing_sft_to_meta_esdt(self, + sender: Address, + collection: str, + num_decimals: int) -> Transaction: + parts = [ + "changeSFTToMetaESDT", + self.serializer.serialize([StringValue(collection)]), + self.serializer.serialize([BigUIntValue(num_decimals)]) + ] + + return TransactionBuilder( + config=self._config, + sender=sender, + receiver=self._config.esdt_contract_address, + amount=0, + gas_limit=self._config.gas_limit_change_sft_to_meta_esdt, + add_data_movement_gas=True, + data_parts=parts + ).build() + + def create_transaction_for_transferring_nft_create_role(self, + sender: Address, + token_identifier: str, + user: Address) -> Transaction: + """This role can be transferred only if the `canTransferNFTCreateRole` property of the token is set to `true`.""" + parts = [ + "transferNFTCreateRole", + self.serializer.serialize([StringValue(token_identifier)]), + sender.to_hex(), + user.to_hex() + ] + + return TransactionBuilder( + config=self._config, + sender=sender, + receiver=self._config.esdt_contract_address, + amount=0, + gas_limit=self._config.gas_limit_transfer_nft_create_role, + add_data_movement_gas=True, + data_parts=parts + ).build() + + def create_transaction_for_stopping_nft_creation(self, + sender: Address, + token_identifier: str) -> Transaction: + parts = [ + "stopNFTCreate", + self.serializer.serialize([StringValue(token_identifier)]) + ] + + return TransactionBuilder( + config=self._config, + sender=sender, + receiver=self._config.esdt_contract_address, + amount=0, + gas_limit=self._config.gas_limit_stop_nft_create, + add_data_movement_gas=True, + data_parts=parts + ).build() + + def create_transaction_for_wiping_single_nft(self, + sender: Address, + token_identifier: str, + token_nonce: int, + user: Address) -> Transaction: + parts = [ + "wipeSingleNFT", + self.serializer.serialize([StringValue(token_identifier)]), + self.serializer.serialize([BigUIntValue(token_nonce)]), + user.to_hex() + ] + + return TransactionBuilder( + config=self._config, + sender=sender, + receiver=self._config.esdt_contract_address, + amount=0, + gas_limit=self._config.gas_limit_wipe_single_nft, + add_data_movement_gas=True, + data_parts=parts + ).build() + + def create_transction_for_adding_uris(self, + sender: Address, + token_identifier: str, + uris: list[str]) -> Transaction: + parts = ["ESDTNFTAddURI"] + + serialized_parts = self.serializer.serialize_to_parts([ + StringValue(token_identifier), + *map(StringValue, uris) + ]) + + parts.extend([part.hex() for part in serialized_parts]) + + return TransactionBuilder( + config=self._config, + sender=sender, + receiver=sender, + amount=0, + gas_limit=self._config.gas_limit_esdt_nft_add_uri, + add_data_movement_gas=True, + data_parts=parts + ).build() + def _bool_to_typed_string(self, value: bool) -> StringValue: if value: return StringValue("true") diff --git a/multiversx_sdk/token_management/token_management_transactions_factory_test.py b/multiversx_sdk/token_management/token_management_transactions_factory_test.py index 9f5f0612..a83be540 100644 --- a/multiversx_sdk/token_management/token_management_transactions_factory_test.py +++ b/multiversx_sdk/token_management/token_management_transactions_factory_test.py @@ -259,7 +259,8 @@ def test_set_all_roles_on_fungible_token(): ) assert transaction.data - assert transaction.data.decode() == f"setSpecialRole@4652414e4b2d313163653365@1e8a8b6b49de5b7be10aaa158a5a6a4abb4b56cc08f524bb5e6cd5f211ad3e13@{mint_role_as_hex}@{burn_role_as_hex}@{transfer_role_as_hex}" + assert transaction.data.decode() == f"setSpecialRole@4652414e4b2d313163653365@1e8a8b6b49de5b7be10aaa158a5a6a4abb4b56cc08f524bb5e6cd5f211ad3e13@{ + mint_role_as_hex}@{burn_role_as_hex}@{transfer_role_as_hex}" assert transaction.sender == frank assert transaction.value == 0 @@ -640,3 +641,117 @@ def test_register_dynamic_and_set_all_roles_meta(): assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" assert transaction.value == 50000000000000000 assert transaction.gas_limit == 60_159_500 + + +def test_transfer_ownership(): + transaction = factory.create_transaction_for_transferring_ownership( + sender=alice, + token_identifier="AND-1d56f2", + new_owner=frank + ) + + assert transaction.data.decode() == "transferOwnership@414e442d316435366632@b37f5d130beb8885b90ab574a8bfcdd894ca531a7d3d1f3431158d77d6185fbb" + assert transaction.sender == alice + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + assert transaction.value == 0 + assert transaction.gas_limit == 60_204_500 + + +def test_create_transaction_for_freezing_single_nft(): + transaction = factory.create_transaction_for_freezing_single_nft( + sender=alice, + token_identifier="TEST-123456", + token_nonce=1, + user=frank + ) + + assert transaction.data.decode() == "freezeSingleNFT@544553542d313233343536@01@b37f5d130beb8885b90ab574a8bfcdd894ca531a7d3d1f3431158d77d6185fbb" + assert transaction.sender == alice + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + assert transaction.value == 0 + assert transaction.gas_limit == 60_209_000 + + +def test_create_transaction_for_unfreezing_single_nft(): + transaction = factory.create_transaction_for_unfreezing_single_nft( + sender=alice, + token_identifier="TEST-123456", + token_nonce=1, + user=frank + ) + + assert transaction.data.decode() == "unFreezeSingleNFT@544553542d313233343536@01@b37f5d130beb8885b90ab574a8bfcdd894ca531a7d3d1f3431158d77d6185fbb" + assert transaction.sender == alice + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + assert transaction.value == 0 + assert transaction.gas_limit == 60_212_000 + + +def test_create_transaction_for_changing_sft_to_meta_esdt(): + transaction = factory.create_transaction_for_changing_sft_to_meta_esdt( + sender=alice, + collection="SFT-123456", + num_decimals=6, + ) + + assert transaction.data.decode() == "changeSFTToMetaESDT@5346542d313233343536@06" + assert transaction.sender == alice + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + assert transaction.value == 0 + assert transaction.gas_limit == 60_114_500 + + +def test_create_transaction_for_transferring_nft_create_role(): + transaction = factory.create_transaction_for_transferring_nft_create_role( + sender=alice, + token_identifier="SFT-123456", + user=frank, + ) + + assert transaction.data.decode() == "transferNFTCreateRole@5346542d313233343536@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@b37f5d130beb8885b90ab574a8bfcdd894ca531a7d3d1f3431158d77d6185fbb" + assert transaction.sender == alice + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + assert transaction.value == 0 + assert transaction.gas_limit == 60_308_000 + + +def test_create_transaction_for_stopping_nft_creation(): + transaction = factory.create_transaction_for_stopping_nft_creation( + sender=alice, + token_identifier="SFT-123456" + ) + + assert transaction.data.decode() == "stopNFTCreate@5346542d313233343536" + assert transaction.sender == alice + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + assert transaction.value == 0 + assert transaction.gas_limit == 60_101_000 + + +def test_create_transaction_for_wiping_single_nft(): + transaction = factory.create_transaction_for_wiping_single_nft( + sender=alice, + token_identifier="SFT-123456", + token_nonce=10, + user=frank, + ) + + assert transaction.data.decode() == "wipeSingleNFT@5346542d313233343536@0a@b37f5d130beb8885b90ab574a8bfcdd894ca531a7d3d1f3431158d77d6185fbb" + assert transaction.sender == alice + assert transaction.receiver.to_bech32() == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + assert transaction.value == 0 + assert transaction.gas_limit == 60_203_000 + + +def test_create_transaction_for_adding_uris(): + transaction = factory.create_transction_for_adding_uris( + sender=alice, + token_identifier="SFT-123456", + uris=["firstURI", "secondURI"] + ) + + assert transaction.data.decode() == "ESDTNFTAddURI@5346542d313233343536@6669727374555249@7365636f6e64555249" + assert transaction.sender == alice + assert transaction.receiver == alice + assert transaction.value == 0 + assert transaction.gas_limit == 10_155_000