diff --git a/multiversx_sdk/converters/transactions_converter.py b/multiversx_sdk/converters/transactions_converter.py index dd0b1481..4a60ce84 100644 --- a/multiversx_sdk/converters/transactions_converter.py +++ b/multiversx_sdk/converters/transactions_converter.py @@ -33,7 +33,9 @@ def transaction_to_dictionary(self, transaction: ITransaction) -> Dict[str, Any] "options": transaction.options, "guardian": transaction.guardian, "signature": self._value_to_hex_or_empty(transaction.signature), - "guardianSignature": self._value_to_hex_or_empty(transaction.guardian_signature) + "guardianSignature": self._value_to_hex_or_empty(transaction.guardian_signature), + "relayer": transaction.relayer, + "relayerSignature": self._value_to_hex_or_empty(transaction.relayer_signature) } def dictionary_to_transaction(self, dictionary: Dict[str, Any]) -> Transaction: @@ -55,6 +57,8 @@ def dictionary_to_transaction(self, dictionary: Dict[str, Any]) -> Transaction: options=dictionary.get("options", None), signature=self._bytes_from_hex(dictionary.get("signature", "")), guardian_signature=self._bytes_from_hex(dictionary.get("guardianSignature", "")), + relayer=dictionary.get("relayer", None), + relayer_signature=self._bytes_from_hex(dictionary.get("relayerSignature", "")) ) def transaction_on_network_to_outcome(self, transaction_on_network: TransactionOnNetwork) -> TransactionOutcome: diff --git a/multiversx_sdk/core/interfaces.py b/multiversx_sdk/core/interfaces.py index 47aeb20e..ef9974d8 100644 --- a/multiversx_sdk/core/interfaces.py +++ b/multiversx_sdk/core/interfaces.py @@ -25,6 +25,8 @@ class ITransaction(Protocol): guardian: str signature: bytes guardian_signature: bytes + relayer: str + relayer_signature: bytes class IMessage(Protocol): diff --git a/multiversx_sdk/core/proto/transaction.proto b/multiversx_sdk/core/proto/transaction.proto index 6e38de1f..f240bb6b 100644 --- a/multiversx_sdk/core/proto/transaction.proto +++ b/multiversx_sdk/core/proto/transaction.proto @@ -6,19 +6,21 @@ package proto; // Transaction holds all the data needed for a value transfer or SC call message Transaction { - uint64 Nonce = 1; - bytes Value = 2; - bytes RcvAddr = 3; - bytes RcvUserName = 4; - bytes SndAddr = 5; - bytes SndUserName = 6; - uint64 GasPrice = 7; - uint64 GasLimit = 8; - bytes Data = 9; - bytes ChainID = 10; - uint32 Version = 11; - bytes Signature = 12; - uint32 Options = 13; - bytes GuardAddr = 14; - bytes GuardSignature = 15; + uint64 Nonce = 1; + bytes Value = 2; + bytes RcvAddr = 3; + bytes RcvUserName = 4; + bytes SndAddr = 5; + bytes SndUserName = 6; + uint64 GasPrice = 7; + uint64 GasLimit = 8; + bytes Data = 9; + bytes ChainID = 10; + uint32 Version = 11; + bytes Signature = 12; + uint32 Options = 13; + bytes GuardAddr = 14; + bytes GuardSignature = 15; + bytes Relayer = 16; + bytes RelayerSignature = 17; } diff --git a/multiversx_sdk/core/proto/transaction_pb2.py b/multiversx_sdk/core/proto/transaction_pb2.py index 3e3b677a..770b02c2 100644 --- a/multiversx_sdk/core/proto/transaction_pb2.py +++ b/multiversx_sdk/core/proto/transaction_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11transaction.proto\x12\x05proto\"\x9a\x02\n\x0bTransaction\x12\r\n\x05Nonce\x18\x01 \x01(\x04\x12\r\n\x05Value\x18\x02 \x01(\x0c\x12\x0f\n\x07RcvAddr\x18\x03 \x01(\x0c\x12\x13\n\x0bRcvUserName\x18\x04 \x01(\x0c\x12\x0f\n\x07SndAddr\x18\x05 \x01(\x0c\x12\x13\n\x0bSndUserName\x18\x06 \x01(\x0c\x12\x10\n\x08GasPrice\x18\x07 \x01(\x04\x12\x10\n\x08GasLimit\x18\x08 \x01(\x04\x12\x0c\n\x04\x44\x61ta\x18\t \x01(\x0c\x12\x0f\n\x07\x43hainID\x18\n \x01(\x0c\x12\x0f\n\x07Version\x18\x0b \x01(\r\x12\x11\n\tSignature\x18\x0c \x01(\x0c\x12\x0f\n\x07Options\x18\r \x01(\r\x12\x11\n\tGuardAddr\x18\x0e \x01(\x0c\x12\x16\n\x0eGuardSignature\x18\x0f \x01(\x0c\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11transaction.proto\x12\x05proto\"\xc5\x02\n\x0bTransaction\x12\r\n\x05Nonce\x18\x01 \x01(\x04\x12\r\n\x05Value\x18\x02 \x01(\x0c\x12\x0f\n\x07RcvAddr\x18\x03 \x01(\x0c\x12\x13\n\x0bRcvUserName\x18\x04 \x01(\x0c\x12\x0f\n\x07SndAddr\x18\x05 \x01(\x0c\x12\x13\n\x0bSndUserName\x18\x06 \x01(\x0c\x12\x10\n\x08GasPrice\x18\x07 \x01(\x04\x12\x10\n\x08GasLimit\x18\x08 \x01(\x04\x12\x0c\n\x04\x44\x61ta\x18\t \x01(\x0c\x12\x0f\n\x07\x43hainID\x18\n \x01(\x0c\x12\x0f\n\x07Version\x18\x0b \x01(\r\x12\x11\n\tSignature\x18\x0c \x01(\x0c\x12\x0f\n\x07Options\x18\r \x01(\r\x12\x11\n\tGuardAddr\x18\x0e \x01(\x0c\x12\x16\n\x0eGuardSignature\x18\x0f \x01(\x0c\x12\x0f\n\x07Relayer\x18\x10 \x01(\x0c\x12\x18\n\x10RelayerSignature\x18\x11 \x01(\x0c\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'transaction_pb2', globals()) @@ -21,5 +21,5 @@ DESCRIPTOR._options = None _TRANSACTION._serialized_start=29 # pyright: ignore[reportUndefinedVariable] - _TRANSACTION._serialized_end=311 # pyright: ignore[reportUndefinedVariable] + _TRANSACTION._serialized_end=354 # pyright: ignore[reportUndefinedVariable] # @@protoc_insertion_point(module_scope) diff --git a/multiversx_sdk/core/proto/transaction_pb2.pyi b/multiversx_sdk/core/proto/transaction_pb2.pyi index 047a4fc8..ee00c2e5 100644 --- a/multiversx_sdk/core/proto/transaction_pb2.pyi +++ b/multiversx_sdk/core/proto/transaction_pb2.pyi @@ -5,7 +5,7 @@ from typing import ClassVar as _ClassVar, Optional as _Optional DESCRIPTOR: _descriptor.FileDescriptor class Transaction(_message.Message): - __slots__ = ["ChainID", "Data", "GasLimit", "GasPrice", "GuardAddr", "GuardSignature", "Nonce", "Options", "RcvAddr", "RcvUserName", "Signature", "SndAddr", "SndUserName", "Value", "Version"] + __slots__ = ["ChainID", "Data", "GasLimit", "GasPrice", "GuardAddr", "GuardSignature", "Nonce", "Options", "RcvAddr", "RcvUserName", "Relayer", "RelayerSignature", "Signature", "SndAddr", "SndUserName", "Value", "Version"] CHAINID_FIELD_NUMBER: _ClassVar[int] ChainID: bytes DATA_FIELD_NUMBER: _ClassVar[int] @@ -24,8 +24,12 @@ class Transaction(_message.Message): Options: int RCVADDR_FIELD_NUMBER: _ClassVar[int] RCVUSERNAME_FIELD_NUMBER: _ClassVar[int] + RELAYERSIGNATURE_FIELD_NUMBER: _ClassVar[int] + RELAYER_FIELD_NUMBER: _ClassVar[int] RcvAddr: bytes RcvUserName: bytes + Relayer: bytes + RelayerSignature: bytes SIGNATURE_FIELD_NUMBER: _ClassVar[int] SNDADDR_FIELD_NUMBER: _ClassVar[int] SNDUSERNAME_FIELD_NUMBER: _ClassVar[int] @@ -36,4 +40,4 @@ class Transaction(_message.Message): VERSION_FIELD_NUMBER: _ClassVar[int] Value: bytes Version: int - def __init__(self, Nonce: _Optional[int] = ..., Value: _Optional[bytes] = ..., RcvAddr: _Optional[bytes] = ..., RcvUserName: _Optional[bytes] = ..., SndAddr: _Optional[bytes] = ..., SndUserName: _Optional[bytes] = ..., GasPrice: _Optional[int] = ..., GasLimit: _Optional[int] = ..., Data: _Optional[bytes] = ..., ChainID: _Optional[bytes] = ..., Version: _Optional[int] = ..., Signature: _Optional[bytes] = ..., Options: _Optional[int] = ..., GuardAddr: _Optional[bytes] = ..., GuardSignature: _Optional[bytes] = ...) -> None: ... + def __init__(self, Nonce: _Optional[int] = ..., Value: _Optional[bytes] = ..., RcvAddr: _Optional[bytes] = ..., RcvUserName: _Optional[bytes] = ..., SndAddr: _Optional[bytes] = ..., SndUserName: _Optional[bytes] = ..., GasPrice: _Optional[int] = ..., GasLimit: _Optional[int] = ..., Data: _Optional[bytes] = ..., ChainID: _Optional[bytes] = ..., Version: _Optional[int] = ..., Signature: _Optional[bytes] = ..., Options: _Optional[int] = ..., GuardAddr: _Optional[bytes] = ..., GuardSignature: _Optional[bytes] = ..., Relayer: _Optional[bytes] = ..., RelayerSignature: _Optional[bytes] = ...) -> None: ... diff --git a/multiversx_sdk/core/proto/transaction_serializer.py b/multiversx_sdk/core/proto/transaction_serializer.py index b0176a0e..69e4b157 100644 --- a/multiversx_sdk/core/proto/transaction_serializer.py +++ b/multiversx_sdk/core/proto/transaction_serializer.py @@ -21,6 +21,8 @@ class ITransaction(Protocol): guardian: str signature: bytes guardian_signature: bytes + relayer: str + relayer_signature: bytes class ProtoSerializer: @@ -66,4 +68,8 @@ def convert_to_proto_message(self, transaction: ITransaction) -> ProtoTransactio proto_transaction.GuardAddr = Address.new_from_bech32(guardian_address).get_public_key() proto_transaction.GuardSignature = transaction.guardian_signature + if transaction.relayer: + proto_transaction.Relayer = Address.new_from_bech32(transaction.relayer).get_public_key() + proto_transaction.RelayerSignature = transaction.relayer_signature + return proto_transaction diff --git a/multiversx_sdk/core/transaction.py b/multiversx_sdk/core/transaction.py index 0f87e289..83dd13b0 100644 --- a/multiversx_sdk/core/transaction.py +++ b/multiversx_sdk/core/transaction.py @@ -21,7 +21,9 @@ def __init__(self, options: Optional[int] = None, guardian: Optional[str] = None, signature: Optional[bytes] = None, - guardian_signature: Optional[bytes] = None) -> None: + guardian_signature: Optional[bytes] = None, + relayer: Optional[str] = None, + relayer_signature: Optional[bytes] = None) -> None: self.chain_id = chain_id self.sender = sender self.receiver = receiver @@ -42,6 +44,9 @@ def __init__(self, self.guardian = guardian or "" self.guardian_signature = guardian_signature or bytes() + self.relayer = relayer or "" + self.relayer_signature = relayer_signature or bytes() + def __eq__(self, other: object) -> bool: if not isinstance(other, Transaction): return False diff --git a/multiversx_sdk/core/transactions_factories/relayed_transactions_factory.py b/multiversx_sdk/core/transactions_factories/relayed_transactions_factory.py index 0bb840f7..8dfe2fb4 100644 --- a/multiversx_sdk/core/transactions_factories/relayed_transactions_factory.py +++ b/multiversx_sdk/core/transactions_factories/relayed_transactions_factory.py @@ -13,6 +13,7 @@ class IConfig(Protocol): chain_id: str min_gas_limit: int gas_limit_per_byte: int + extra_gas_limit_for_relayed_v3: int class RelayedTransactionsFactory: @@ -72,6 +73,29 @@ def create_relayed_v2_transaction(self, options=inner_transaction.options ) + def create_relayed_v3_transaction(self, + transaction: ITransaction, + relayer_address: IAddress) -> Transaction: + """Relayer address must be in the same shard with sender address.""" + gas_limit = transaction.gas_limit + self._config.extra_gas_limit_for_relayed_v3 + + return Transaction( + sender=transaction.sender, + receiver=transaction.receiver, + gas_limit=gas_limit, + chain_id=transaction.chain_id, + nonce=transaction.nonce, + value=transaction.value, + sender_username=transaction.sender_username, + receiver_username=transaction.receiver_username, + gas_price=transaction.gas_price, + data=transaction.data, + version=transaction.version, + options=transaction.options, + guardian=transaction.guardian, + relayer=relayer_address.to_bech32() + ) + def _prepare_inner_transaction_for_relayed_v1(self, inner_transaction: ITransaction) -> str: sender = Address.new_from_bech32(inner_transaction.sender).to_hex() receiver = Address.new_from_bech32(inner_transaction.receiver).to_hex() diff --git a/multiversx_sdk/core/transactions_factories/relayed_transactions_factory_test.py b/multiversx_sdk/core/transactions_factories/relayed_transactions_factory_test.py index 056cf1e9..0faa03a8 100644 --- a/multiversx_sdk/core/transactions_factories/relayed_transactions_factory_test.py +++ b/multiversx_sdk/core/transactions_factories/relayed_transactions_factory_test.py @@ -231,3 +231,29 @@ def test_compute_relayed_v2_transaction(self): assert relayed_transaction.options == 0 assert relayed_transaction.gas_limit == 60414500 assert relayed_transaction.data.decode() == "relayedTxV2@000000000000000000010000000000000000000000000000000000000002ffff@0f@676574436f6e7472616374436f6e666967@fc3ed87a51ee659f937c1a1ed11c1ae677e99629fae9cc289461f033e6514d1a8cfad1144ae9c1b70f28554d196bd6ba1604240c1c1dc19c959e96c1c3b62d0c" + + def test_relayed_v3(self): + alice = self.wallets["alice"] + alice_address = Address.new_from_bech32(alice.label) + bob = self.wallets["bob"] + + tx = Transaction( + sender=bob.label, + receiver="erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + gas_limit=1_000_000, + chain_id=self.config.chain_id, + data=b"add@07", + nonce=15, + version=2, + options=0 + ) + + relayed_tx = self.factory.create_relayed_v3_transaction( + transaction=tx, + relayer_address=alice_address + ) + + assert relayed_tx.sender == bob.label + assert relayed_tx.receiver == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + assert relayed_tx.relayer == alice.label + assert relayed_tx.gas_limit == 1_050_000 diff --git a/multiversx_sdk/core/transactions_factories/transactions_factory_config.py b/multiversx_sdk/core/transactions_factories/transactions_factory_config.py index 966681ac..dd311030 100644 --- a/multiversx_sdk/core/transactions_factories/transactions_factory_config.py +++ b/multiversx_sdk/core/transactions_factories/transactions_factory_config.py @@ -60,3 +60,5 @@ def __init__(self, chain_id: str) -> None: # Configuration for smart contract operations self.gas_limit_claim_developer_rewards = 6_000_000 self.gas_limit_change_owner_address = 6_000_000 + + self.extra_gas_limit_for_relayed_v3 = 50_000 diff --git a/multiversx_sdk/network_providers/api_network_provider_test.py b/multiversx_sdk/network_providers/api_network_provider_test.py index 2ebd82ac..adf7c6d8 100644 --- a/multiversx_sdk/network_providers/api_network_provider_test.py +++ b/multiversx_sdk/network_providers/api_network_provider_test.py @@ -87,7 +87,7 @@ def test_get_nonfungible_token_of_account(self): address = Address.new_from_bech32('erd1487vz5m4zpxjyqw4flwa3xhnkzg4yrr3mkzf5sf0zgt94hjprc8qazcccl') result = self.api.get_nonfungible_token_of_account(address, "NFTEST-ec88b8", 1) - assert result.balance == 0 + assert result.balance == 1 assert result.nonce == 1 assert result.collection == "NFTEST-ec88b8" assert result.identifier == "NFTEST-ec88b8-01" diff --git a/multiversx_sdk/network_providers/transactions.py b/multiversx_sdk/network_providers/transactions.py index a03264d9..87ba3093 100644 --- a/multiversx_sdk/network_providers/transactions.py +++ b/multiversx_sdk/network_providers/transactions.py @@ -28,6 +28,8 @@ class ITransaction(Protocol): guardian: str signature: bytes guardian_signature: bytes + relayer: str + relayer_signature: bytes class TransactionOnNetwork: