diff --git a/examples/add_message_as_dict.py b/examples/add_message_as_dict.py new file mode 100644 index 0000000..43610dd --- /dev/null +++ b/examples/add_message_as_dict.py @@ -0,0 +1,33 @@ +from mospy import Account, Transaction +from mospy.clients import HTTPClient + +account = Account( + seed_phrase="", + hrp='elys' +) +tx = Transaction( + account=account, + chain_id='elystestnet-1', + gas=800000, +) + + +msg = { + "creator": account.address, + "amount": "1000" +} + + +tx.add_dict_msg(msg, type_url="/elys.stablestake.MsgBond") + +client = HTTPClient( + api="https://api.testnet.elys.network" +) + +tx.set_fee( + amount=100, + denom="uelys" +) + +client.load_account_data(account=account) +response = client.broadcast_transaction(transaction=tx) diff --git a/examples/evmos_transfer.py b/examples/evmos_transfer.py index e17ba3c..bebdab7 100644 --- a/examples/evmos_transfer.py +++ b/examples/evmos_transfer.py @@ -28,7 +28,7 @@ tx.add_msg( tx_type="transfer", sender=account, - receipient=account.address, + recipient=account.address, amount=3500000000000000, denom="aevmos", ) diff --git a/pyproject.toml b/pyproject.toml index 4f7022b..f497b72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mospy-wallet" -version = "0.5.5" +version = "0.6.0" description = "This package is a fork of cosmospy and is a light framework for the cosmos ecosystem" authors = [ { name = "ctrl-felix", email = "dev@ctrl-felix.de" }, diff --git a/src/mospy/Transaction.py b/src/mospy/Transaction.py index 50ca5d0..3f046b5 100644 --- a/src/mospy/Transaction.py +++ b/src/mospy/Transaction.py @@ -5,8 +5,10 @@ import ecdsa from sha3 import keccak_256 from google.protobuf import any_pb2 as any + from mospy._transactions import ALL_TRANSACTION_HELPERS from mospy.Account import Account +from mospy.protobuf.GenericProtobuf import GenericProtobuf built_in_transactions = {} for transaction_adapter in ALL_TRANSACTION_HELPERS: @@ -97,6 +99,26 @@ def add_raw_msg(self, unpacked_msg, type_url: str) -> None: msg_any.type_url = type_url self._tx_body.messages.append(msg_any) + def add_dict_msg(self, msg_dict: dict, type_url: str) -> None: + """ + Add a message as dictionary to the tx body manually. + + Args: + msg_dict: Transaction dict + type_url: Type url for the transaction + """ + generic_proto = GenericProtobuf() + msg_any = generic_proto.create_any_message(type_url=type_url, msg_dict=msg_dict) + self._tx_body.messages.append(msg_any) + + def add_send_msg(self, recipient: str, amount: int, denom: str = "uatom") -> None: + self.add_msg( + tx_type="transfer", + sender=self._account, + recipient=recipient, + amount=amount, + denom=denom, + ) def set_fee(self, amount: int, denom: str = "uatom"): """ Set the fee manually diff --git a/src/mospy/_transactions/_transfer.py b/src/mospy/_transactions/_transfer.py index 1c0f771..e5947b7 100644 --- a/src/mospy/_transactions/_transfer.py +++ b/src/mospy/_transactions/_transfer.py @@ -11,7 +11,7 @@ def __init__( self, protobuf_package: str, sender: Account, - receipient: str, + recipient: str, amount: int, denom: str, ): @@ -30,12 +30,12 @@ def __init__( _tx_coin.amount = str(amount) self._amount = _tx_coin self._sender = sender.address - self._receipient = receipient + self._recipient = recipient def format(self) -> (str, object): msg = self._tx_pb2.MsgSend( from_address=self._sender, - to_address=self._receipient, + to_address=self._recipient, ) msg.amount.append(self._amount) diff --git a/src/mospy/protobuf/GenericProtobuf.py b/src/mospy/protobuf/GenericProtobuf.py new file mode 100644 index 0000000..c102224 --- /dev/null +++ b/src/mospy/protobuf/GenericProtobuf.py @@ -0,0 +1,78 @@ +from google.protobuf import descriptor_pb2, descriptor_pool, message_factory, any_pb2 +from google.protobuf.json_format import ParseDict +import json + +class GenericProtobuf: + def __init__(self): + self.pool = descriptor_pool.Default() + self.factory = message_factory.MessageFactory() + + def create_message_type(self, type_url, fields): + # Create a DescriptorProto for the message type + type_name = type_url.split('.')[-1] + + # Check if the type is already existing in the pool + try: + self.pool.FindMessageTypeByName(type_name) + except KeyError: + pass + else: + return + + descriptor_proto = descriptor_pb2.DescriptorProto() + descriptor_proto.name = type_name + + # Add key - value pairs as fields to the Descriptor + for idx, (field_name, field_value) in enumerate(fields.items(), start=1): + field_descriptor = descriptor_proto.field.add() + field_descriptor.name = field_name + field_descriptor.number = idx + + if isinstance(field_value, int): + field_descriptor.type = descriptor_pb2.FieldDescriptorProto.TYPE_INT32 + elif isinstance(field_value, float): + field_descriptor.type = descriptor_pb2.FieldDescriptorProto.TYPE_FLOAT + elif isinstance(field_value, bool): + field_descriptor.type = descriptor_pb2.FieldDescriptorProto.TYPE_BOOL + elif isinstance(field_value, list): + nested_type_name = f"{type_name}_{field_name}" + self.create_message_type(nested_type_name, field_value[0]) + field_descriptor.type = descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE + field_descriptor.type_name = f"{nested_type_name}" + field_descriptor.label = descriptor_pb2.FieldDescriptorProto.LABEL_REPEATED + elif isinstance(field_value, dict): + nested_type_name = f"{type_name}_{field_name}" + self.create_message_type(nested_type_name, field_value[0]) + field_descriptor.type = descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE + field_descriptor.type_name = f"{nested_type_name}" + + else: + field_descriptor.type = descriptor_pb2.FieldDescriptorProto.TYPE_STRING + + # Create a FileDescriptorProto for the new descriptor + file_descriptor_proto = descriptor_pb2.FileDescriptorProto() + file_descriptor_proto.name = f'{type_name}.proto' + file_descriptor_proto.message_type.add().MergeFrom(descriptor_proto) + + # Add the file descriptor + self.pool.Add(file_descriptor_proto) + + def get_message_class(self, type_name): + message_descriptor = self.pool.FindMessageTypeByName(type_name.split('.')[-1]) + return self.factory.GetPrototype(message_descriptor) + + def create_any_message(self, msg_dict, type_url): + type_name = type_url.split('/')[-1] + + self.create_message_type(type_name, msg_dict) + + message_class = self.get_message_class(type_name) + + message_instance = message_class() + ParseDict(msg_dict, message_instance) + + msg_any = any_pb2.Any() + msg_any.Pack(message_instance) + msg_any.type_url = type_url + + return msg_any diff --git a/tests/clients/test_grpcclient.py b/tests/clients/test_grpcclient.py index 5a9029a..eb45d19 100644 --- a/tests/clients/test_grpcclient.py +++ b/tests/clients/test_grpcclient.py @@ -45,7 +45,7 @@ def test_transaction_submitting(self): tx.add_msg( tx_type="transfer", sender=account, - receipient=account.address, + recipient=account.address, amount=1000, denom="uatom", ) diff --git a/tests/clients/test_httpclient.py b/tests/clients/test_httpclient.py index 13dc8ae..0e8f807 100644 --- a/tests/clients/test_httpclient.py +++ b/tests/clients/test_httpclient.py @@ -61,7 +61,7 @@ def test_transaction_submitting(self): tx.add_msg( tx_type="transfer", sender=account, - receipient=account.address, + recipient=account.address, amount=1000, denom="uatom", ) diff --git a/tests/test_eth.py b/tests/test_eth.py index ec56d61..85d74b0 100644 --- a/tests/test_eth.py +++ b/tests/test_eth.py @@ -28,7 +28,7 @@ def test_eth_transaction_creation(self): tx.add_msg( tx_type="transfer", sender=account, - receipient=account.address, + recipient=account.address, amount=3500000000000000, denom="aevmos", ) diff --git a/tests/test_transaction.py b/tests/test_transaction.py index b9399ba..4115ca2 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -3,14 +3,13 @@ from mospy import Transaction expected_tx_bytes = "CpABCo0BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm0KLWNvc21vczFxZWNuMHVqcDRydzhobjkzbDlqcHN4eXc0ZmEyOGE1MjM3YTRweBItY29zbW9zMXRrdjlycXV4cjg4cjdzbnJnNDJreGRqOWdzbmZ4eGcwMjhrdWg5Gg0KBXVhdG9tEgQxMDAwEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQIkQVvG9OBetDe7bYUgl2vwgbJFxmmGuquytVSEwhQ0uBIECgIIARgBEhIKDQoFdWF0b20SBDEwMDAQ6AcaQJqww3jDNgn4UMDpaFq34xPbdwTAsn4VnRvZ1rjYGCMEa6fDKnu9T5xlQV5IEpCDeMzmNBEhTo9QtcOIzjjPzes=" - +expected_tx_bytes2 = "Ck8KTQoZL2VseXMuc3RhYmxlc3Rha2UuTXNnQm9uZBIwCitlbHlzMXFlY24wdWpwNHJ3OGhuOTNsOWpwc3h5dzRmYTI4YTUyMzd5anZ5EgExEmYKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQIkQVvG9OBetDe7bYUgl2vwgbJFxmmGuquytVSEwhQ0uBIECgIIARgFEhIKDAoFdWVseXMSAzEyNRCgwh4aQLoR5z302GPGh9YfAS4lM/JeRLrkkNW0lEdc94mkYG3qTiWuJMLDL5653PkJ/w9qZOBjOcZn1huVDu7XTzUSajo=" class TestTransactionClass: seed_phrase = "law grab theory better athlete submit awkward hawk state wedding wave monkey audit blame fury wood tag rent furnace exotic jeans drift destroy style" def test_transaction_creation(self): account = Account( - seed_phrase= - "law grab theory better athlete submit awkward hawk state wedding wave monkey audit blame fury wood tag rent furnace exotic jeans drift destroy style", + seed_phrase=self.seed_phrase, account_number=1, next_sequence=1, ) @@ -26,7 +25,7 @@ def test_transaction_creation(self): tx.add_msg( tx_type="transfer", sender=account, - receipient="cosmos1tkv9rquxr88r7snrg42kxdj9gsnfxxg028kuh9", + recipient="cosmos1tkv9rquxr88r7snrg42kxdj9gsnfxxg028kuh9", amount=1000, denom="uatom", ) @@ -35,3 +34,31 @@ def test_transaction_creation(self): tx_bytes = tx.get_tx_bytes_as_string() assert tx_bytes == expected_tx_bytes + def test_transaction_creation_from_dict(self): + account = Account( + seed_phrase=self.seed_phrase, + hrp='elys', + account_number=114923, + next_sequence=5 + ) + tx = Transaction( + account=account, + chain_id='elystestnet-1', + gas=500000, + ) + + msg = { + "creator": account.address, + "amount": "1" + } + + tx.add_dict_msg(msg, type_url="/elys.stablestake.MsgBond") + + + tx.set_fee( + amount=125, + denom="uelys" + ) + + tx_bytes = tx.get_tx_bytes_as_string() + assert tx_bytes == expected_tx_bytes2