diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt index 811c8a6b76f..82929dcb8c2 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt @@ -89,6 +89,38 @@ class TestMultiversXSigner { assertEquals("""{"nonce":42,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"$expectedSignature","options":2,"guardian":"$carolBech32"}""", output.encoded) } + @Test + fun signGenericActionWithRelayer() { + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + + val accounts = MultiversX.Accounts.newBuilder() + .setSenderNonce(42) + .setSender(aliceBech32) + .setReceiver(bobBech32) + .setRelayer(carolBech32) + .build() + + val genericAction = MultiversX.GenericAction.newBuilder() + .setAccounts(accounts) + .setValue("1000000000000000000") + .setVersion(2) + .build() + + val signingInput = MultiversX.SigningInput.newBuilder() + .setGenericAction(genericAction) + .setGasPrice(1000000000) + .setGasLimit(100000) + .setChainId("1") + .setPrivateKey(privateKey) + .build() + + val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser()) + val expectedSignature = "f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003" + + assertEquals(expectedSignature, output.signature) + assertEquals("""{"nonce":42,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"$expectedSignature","relayer":"$carolBech32"}""", output.encoded) + } + @Test fun signGenericActionUndelegate() { // Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65 diff --git a/src/MultiversX/Serialization.cpp b/src/MultiversX/Serialization.cpp index dbd7dc25a21..35f2990330f 100644 --- a/src/MultiversX/Serialization.cpp +++ b/src/MultiversX/Serialization.cpp @@ -23,7 +23,8 @@ std::map fields_order{ {"version", 11}, {"signature", 12}, {"options", 13}, - {"guardian", 14}}; + {"guardian", 14}, + {"relayer", 15}}; struct FieldsSorter { bool operator()(const std::string& lhs, const std::string& rhs) const { @@ -69,6 +70,10 @@ sorted_json preparePayload(const MultiversX::Transaction& transaction) { payload["guardian"] = json(transaction.guardian); } + if (!transaction.relayer.empty()) { + payload["relayer"] = json(transaction.relayer); + } + return payload; } diff --git a/src/MultiversX/Transaction.cpp b/src/MultiversX/Transaction.cpp index 54f38c135cb..819cf49b11b 100644 --- a/src/MultiversX/Transaction.cpp +++ b/src/MultiversX/Transaction.cpp @@ -7,11 +7,15 @@ namespace TW::MultiversX { Transaction::Transaction() - : nonce(0), sender(""), senderUsername(""), receiver(""), receiverUsername(""), guardian(""), value("0"), data(""), gasPrice(0), gasLimit(0), chainID(""), version(0), options(TransactionOptions::Default) { + : nonce(0), sender(""), senderUsername(""), receiver(""), receiverUsername(""), guardian(""), relayer(""), value("0"), data(""), gasPrice(0), gasLimit(0), chainID(""), version(0), options(TransactionOptions::Default) { } bool Transaction::hasGuardian() const { return !guardian.empty(); } +bool Transaction::hasRelayer() const { + return !relayer.empty(); +} + } // namespace TW::MultiversX diff --git a/src/MultiversX/Transaction.h b/src/MultiversX/Transaction.h index 518c39b201a..24fce7d9865 100644 --- a/src/MultiversX/Transaction.h +++ b/src/MultiversX/Transaction.h @@ -26,6 +26,7 @@ class Transaction { std::string receiver; std::string receiverUsername; std::string guardian; + std::string relayer; std::string value; std::string data; uint64_t gasPrice; @@ -37,6 +38,7 @@ class Transaction { Transaction(); bool hasGuardian() const; + bool hasRelayer() const; }; } // namespace TW::MultiversX diff --git a/src/MultiversX/TransactionFactory.cpp b/src/MultiversX/TransactionFactory.cpp index 6894ce17f03..b11f0b3de50 100644 --- a/src/MultiversX/TransactionFactory.cpp +++ b/src/MultiversX/TransactionFactory.cpp @@ -41,6 +41,7 @@ Transaction TransactionFactory::fromGenericAction(const Proto::SigningInput& inp transaction.receiver = action.accounts().receiver(); transaction.receiverUsername = action.accounts().receiver_username(); transaction.guardian = action.accounts().guardian(); + transaction.relayer = action.accounts().relayer(); transaction.value = action.value(); transaction.data = action.data(); transaction.gasLimit = input.gas_limit(); @@ -62,6 +63,7 @@ Transaction TransactionFactory::fromEGLDTransfer(const Proto::SigningInput& inpu transaction.receiver = transfer.accounts().receiver(); transaction.receiverUsername = transfer.accounts().receiver_username(); transaction.guardian = transfer.accounts().guardian(); + transaction.relayer = transfer.accounts().relayer(); transaction.value = transfer.amount(); transaction.data = transfer.data(); transaction.gasPrice = coalesceGasPrice(input.gas_price()); @@ -70,7 +72,7 @@ Transaction TransactionFactory::fromEGLDTransfer(const Proto::SigningInput& inpu transaction.options = decideOptions(transaction); // Estimate & set gasLimit: - uint64_t estimatedGasLimit = computeGasLimit(0, 0, transaction.hasGuardian()); + uint64_t estimatedGasLimit = computeGasLimit(0, 0, transaction.hasGuardian(), transaction.hasRelayer()); transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); return transaction; @@ -90,6 +92,7 @@ Transaction TransactionFactory::fromESDTTransfer(const Proto::SigningInput& inpu transaction.receiver = transfer.accounts().receiver(); transaction.receiverUsername = transfer.accounts().receiver_username(); transaction.guardian = transfer.accounts().guardian(); + transaction.relayer = transfer.accounts().relayer(); transaction.value = "0"; transaction.data = data; transaction.gasPrice = coalesceGasPrice(input.gas_price()); @@ -99,7 +102,7 @@ Transaction TransactionFactory::fromESDTTransfer(const Proto::SigningInput& inpu // Estimate & set gasLimit: uint64_t executionGasLimit = this->config.getGasCostESDTTransfer() + this->config.getAdditionalGasForESDTTransfer(); - uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian()); + uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian(), transaction.hasRelayer()); transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); return transaction; @@ -120,6 +123,7 @@ Transaction TransactionFactory::fromESDTNFTTransfer(const Proto::SigningInput& i transaction.sender = transfer.accounts().sender(); transaction.receiver = transfer.accounts().sender(); transaction.guardian = transfer.accounts().guardian(); + transaction.relayer = transfer.accounts().relayer(); transaction.value = "0"; transaction.data = data; transaction.gasPrice = coalesceGasPrice(input.gas_price()); @@ -129,13 +133,13 @@ Transaction TransactionFactory::fromESDTNFTTransfer(const Proto::SigningInput& i // Estimate & set gasLimit: uint64_t executionGasLimit = this->config.getGasCostESDTNFTTransfer() + this->config.getAdditionalGasForESDTNFTTransfer(); - uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian()); + uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian(), transaction.hasRelayer()); transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit); return transaction; } -uint64_t TransactionFactory::computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian) { +uint64_t TransactionFactory::computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian, bool hasRelayer) { uint64_t dataMovementGasLimit = this->config.getMinGasLimit() + this->config.getGasPerDataByte() * dataLength; uint64_t gasLimit = dataMovementGasLimit + executionGasLimit; @@ -143,6 +147,10 @@ uint64_t TransactionFactory::computeGasLimit(size_t dataLength, uint64_t executi gasLimit += this->config.getExtraGasLimitForGuardedTransaction(); } + if (hasRelayer) { + gasLimit += this->config.getExtraGasLimitForRelayedTransaction(); + } + return gasLimit; } diff --git a/src/MultiversX/TransactionFactory.h b/src/MultiversX/TransactionFactory.h index f3a62e019e6..0f2df9edae1 100644 --- a/src/MultiversX/TransactionFactory.h +++ b/src/MultiversX/TransactionFactory.h @@ -46,7 +46,7 @@ class TransactionFactory { Transaction fromESDTNFTTransfer(const Proto::SigningInput& input); private: - uint64_t computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian); + uint64_t computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian, bool hasRelayer); uint64_t coalesceGasLimit(uint64_t providedGasLimit, uint64_t estimatedGasLimit); uint64_t coalesceGasPrice(uint64_t gasPrice); std::string coalesceChainId(std::string chainID); diff --git a/src/MultiversX/TransactionFactoryConfig.cpp b/src/MultiversX/TransactionFactoryConfig.cpp index 1c8ff82f9bb..ca931d4ba62 100644 --- a/src/MultiversX/TransactionFactoryConfig.cpp +++ b/src/MultiversX/TransactionFactoryConfig.cpp @@ -44,6 +44,14 @@ void TransactionFactoryConfig::setExtraGasLimitForGuardedTransaction(uint32_t va this->extraGasLimitForGuardedTransaction = value; } +uint32_t TransactionFactoryConfig::getExtraGasLimitForRelayedTransaction() const { + return this->minGasLimit; +} + +void TransactionFactoryConfig::setExtraGasLimitForRelayedTransaction(uint32_t value) { + this->extraGasLimitForRelayedTransaction = value; +} + uint64_t TransactionFactoryConfig::getMinGasPrice() const { return this->minGasPrice; } @@ -97,6 +105,7 @@ TransactionFactoryConfig TransactionFactoryConfig::GetByTimestamp(uint64_t times config.setGasPerDataByte(1500); config.setMinGasLimit(50000); config.setExtraGasLimitForGuardedTransaction(50000); + config.setExtraGasLimitForRelayedTransaction(50000); config.setMinGasPrice(1000000000); config.setGasCostESDTTransfer(200000); config.setGasCostESDTNFTTransfer(200000); diff --git a/src/MultiversX/TransactionFactoryConfig.h b/src/MultiversX/TransactionFactoryConfig.h index 2cf157edd73..d7d7cb47d92 100644 --- a/src/MultiversX/TransactionFactoryConfig.h +++ b/src/MultiversX/TransactionFactoryConfig.h @@ -17,6 +17,7 @@ class TransactionFactoryConfig { uint32_t gasPerDataByte; uint32_t minGasLimit; uint32_t extraGasLimitForGuardedTransaction; + uint32_t extraGasLimitForRelayedTransaction; uint64_t minGasPrice; /// GasSchedule entries of interest (only one at this moment), according to: https://github.com/multiversx/mx-chain-mainnet-config/blob/master/gasSchedules. @@ -47,6 +48,9 @@ class TransactionFactoryConfig { uint32_t getExtraGasLimitForGuardedTransaction() const; void setExtraGasLimitForGuardedTransaction(uint32_t value); + uint32_t getExtraGasLimitForRelayedTransaction() const; + void setExtraGasLimitForRelayedTransaction(uint32_t value); + uint64_t getMinGasPrice() const; void setMinGasPrice(uint64_t value); diff --git a/src/proto/MultiversX.proto b/src/proto/MultiversX.proto index 02162ef5796..dadbeef1bc9 100644 --- a/src/proto/MultiversX.proto +++ b/src/proto/MultiversX.proto @@ -92,6 +92,9 @@ message Accounts { // Guardian address string guardian = 6; + + // Relayer address + string relayer = 7; } // Input data necessary to create a signed transaction. diff --git a/swift/Tests/Blockchains/MultiversXTests.swift b/swift/Tests/Blockchains/MultiversXTests.swift index 7b8cbf09ffc..2084f8b551d 100644 --- a/swift/Tests/Blockchains/MultiversXTests.swift +++ b/swift/Tests/Blockchains/MultiversXTests.swift @@ -80,6 +80,35 @@ class MultiversXTests: XCTestCase { XCTAssertEqual(output.signature, expectedSignature) XCTAssertEqual(output.encoded, expectedEncoded) } + + func testSignGenericActionWithRelayer() { + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + + let input = MultiversXSigningInput.with { + $0.genericAction = MultiversXGenericAction.with { + $0.accounts = MultiversXAccounts.with { + $0.senderNonce = 42 + $0.sender = aliceBech32 + $0.receiver = bobBech32 + $0.relayer = carolBech32 + } + $0.value = "1000000000000000000" + $0.data = "" + $0.version = 2 + } + $0.gasPrice = 1000000000 + $0.gasLimit = 100000 + $0.chainID = "1" + $0.privateKey = privateKey.data + } + + let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX) + let expectedSignature = "f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003" + let expectedEncoded = #"{"nonce":42,"value":"1000000000000000000","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"\#(expectedSignature)","relayer":"\#(carolBech32)"}"# + + XCTAssertEqual(output.signature, expectedSignature) + XCTAssertEqual(output.encoded, expectedEncoded) + } func testSignGenericActionUndelegate() { // Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65 diff --git a/tests/chains/MultiversX/SerializationTests.cpp b/tests/chains/MultiversX/SerializationTests.cpp index 37651803c7d..c419f3482b7 100644 --- a/tests/chains/MultiversX/SerializationTests.cpp +++ b/tests/chains/MultiversX/SerializationTests.cpp @@ -112,4 +112,29 @@ TEST(MultiversXSerialization, SerializeTransactionWithGuardianAddress) { ASSERT_EQ(expected, actual); } +TEST(MultiversXSerialization, SerializeTransactionWithRelayerAddress) { + Transaction transaction; + transaction.nonce = 42; + transaction.value = "1000000000000000000"; + transaction.sender = ALICE_BECH32; + transaction.receiver = BOB_BECH32; + transaction.relayer = CAROL_BECH32; + transaction.gasPrice = 1000000000; + transaction.gasLimit = 100000; + transaction.chainID = "1"; + transaction.version = 2; + + string expected = + "{" + R"("nonce":42,"value":"1000000000000000000",)" + R"("receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx",)" + R"("sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th",)" + R"("gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,)" + R"("relayer":"erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8")" + "}"; + + string actual = serializeTransaction(transaction); + ASSERT_EQ(expected, actual); +} + } // namespace TW::MultiversX::tests diff --git a/tests/chains/MultiversX/SignerTests.cpp b/tests/chains/MultiversX/SignerTests.cpp index 73b1ed7cf29..09a7ce4e1ad 100644 --- a/tests/chains/MultiversX/SignerTests.cpp +++ b/tests/chains/MultiversX/SignerTests.cpp @@ -5,7 +5,6 @@ #include #include -#include "boost/format.hpp" #include "HexCoding.h" #include "MultiversX/Address.h" #include "MultiversX/Codec.h" @@ -15,6 +14,7 @@ #include "PublicKey.h" #include "TestAccounts.h" #include "TestUtilities.h" +#include "boost/format.hpp" using namespace TW; @@ -608,6 +608,77 @@ TEST(MultiversXSigner, SignEGLDTransferWithGuardian) { assertJSONEqual(expected, nlohmann::json::parse(encoded)); } +TEST(MultiversXSigner, SignGenericActionWithRelayer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(42); + input.mutable_generic_action()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_generic_action()->mutable_accounts()->set_relayer(CAROL_BECH32); + input.mutable_generic_action()->set_value("1000000000000000000"); + input.mutable_generic_action()->set_data(""); + input.mutable_generic_action()->set_version(2); + input.set_gas_price(1000000000); + input.set_gas_limit(100000); + input.set_chain_id("1"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":100000, + "gasPrice":1000000000, + "relayer":"erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "nonce":42, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003", + "value":"1000000000000000000", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + +TEST(MultiversXSigner, SignEGLDTransferWithRelayer) { + auto input = Proto::SigningInput(); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + + input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); + input.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + input.mutable_egld_transfer()->mutable_accounts()->set_relayer(CAROL_BECH32); + input.mutable_egld_transfer()->set_amount("1000000000000000000"); + + auto output = Signer::sign(input); + auto signature = output.signature(); + auto encoded = output.encoded(); + auto expectedSignature = "c86491a51d553889df9fb7ff75880843e2b21aec97ae3e4004b70801a5494a8958af8daf56906f9720b0af6a25ad2ab82b3af05940fb6dfe0dea529f1bf8d90f"; + nlohmann::json expected = R"( + { + "chainID":"1", + "gasLimit":100000, + "gasPrice":1000000000, + "relayer":"erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "nonce":7, + "receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "signature":"c86491a51d553889df9fb7ff75880843e2b21aec97ae3e4004b70801a5494a8958af8daf56906f9720b0af6a25ad2ab82b3af05940fb6dfe0dea529f1bf8d90f", + "value":"1000000000000000000", + "version":2 + })"_json; + + ASSERT_EQ(expectedSignature, signature); + assertJSONEqual(expected, nlohmann::json::parse(encoded)); +} + TEST(ElrondSigner, buildUnsignedTxBytes) { auto input = Proto::SigningInput(); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); diff --git a/tests/chains/MultiversX/TransactionFactoryTests.cpp b/tests/chains/MultiversX/TransactionFactoryTests.cpp index 6b082245973..466e3565924 100644 --- a/tests/chains/MultiversX/TransactionFactoryTests.cpp +++ b/tests/chains/MultiversX/TransactionFactoryTests.cpp @@ -240,4 +240,58 @@ TEST(MultiversXTransactionFactory, createWithGuardian) { ASSERT_EQ(TransactionOptions::Guarded, tx4.options); } +TEST(MultiversXTransactionFactory, createWithRelayer) { + Proto::SigningInput signingInputWithGenericAction; + signingInputWithGenericAction.set_gas_limit(107500); + signingInputWithGenericAction.mutable_generic_action()->set_data("hello"); + signingInputWithGenericAction.mutable_generic_action()->mutable_accounts()->set_relayer(CAROL_BECH32); + + Proto::SigningInput signingInputWithEGLDTransfer; + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->mutable_accounts()->set_relayer(CAROL_BECH32); + signingInputWithEGLDTransfer.mutable_egld_transfer()->set_amount("1"); + + Proto::SigningInput signingInputWithESDTTransfer; + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->mutable_accounts()->set_relayer(CAROL_BECH32); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_token_identifier("MYTOKEN-1234"); + signingInputWithESDTTransfer.mutable_esdt_transfer()->set_amount("10000000000000"); + + Proto::SigningInput signingInputWithESDTNFTTransfer; + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_sender(ALICE_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_receiver(BOB_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->mutable_accounts()->set_relayer(CAROL_BECH32); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_collection("LKMEX-aab910"); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_token_nonce(4); + signingInputWithESDTNFTTransfer.mutable_esdtnft_transfer()->set_amount("184300000000000000"); + + TransactionFactory factory; + Transaction tx1 = factory.create(signingInputWithGenericAction); + Transaction tx2 = factory.create(signingInputWithEGLDTransfer); + Transaction tx3 = factory.create(signingInputWithESDTTransfer); + Transaction tx4 = factory.create(signingInputWithESDTNFTTransfer); + + ASSERT_EQ("hello", tx1.data); + ASSERT_EQ("1", tx2.value); + ASSERT_EQ("ESDTTransfer@4d59544f4b454e2d31323334@09184e72a000", tx3.data); + ASSERT_EQ("ESDTNFTTransfer@4c4b4d45582d616162393130@04@028ec3dfa01ac000@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", tx4.data); + + ASSERT_EQ(TransactionOptions::Default, tx1.options); + ASSERT_EQ(TransactionOptions::Default, tx2.options); + ASSERT_EQ(TransactionOptions::Default, tx3.options); + ASSERT_EQ(TransactionOptions::Default, tx4.options); + + ASSERT_EQ(CAROL_BECH32, tx1.relayer); + ASSERT_EQ(CAROL_BECH32, tx2.relayer); + ASSERT_EQ(CAROL_BECH32, tx3.relayer); + ASSERT_EQ(CAROL_BECH32, tx4.relayer); + + ASSERT_EQ(107500ul, tx1.gasLimit); + ASSERT_EQ(100000ul, tx2.gasLimit); + ASSERT_EQ(475000ul, tx3.gasLimit); + ASSERT_EQ(987500ul, tx4.gasLimit); +} + } // namespace TW::MultiversX::tests