From 8ef1414dcc4b05ea38b5653b135167673aa5adb5 Mon Sep 17 00:00:00 2001 From: Rob Walworth <110835868+rwalworth@users.noreply.github.com> Date: Tue, 5 Sep 2023 09:51:43 -0500 Subject: [PATCH] Add `AutoCreateAccountTransferTransactionExample` (#495) Signed-off-by: Rob Walworth Signed-off-by: Deyan Zhekov Co-authored-by: Deyan Zhekov --- ...CreateAccountTransferTransactionExample.cc | 126 ++++++++++++++++++ sdk/examples/CMakeLists.txt | 4 + sdk/main/include/TransactionReceipt.h | 31 ++++- sdk/main/include/TransactionReceiptQuery.h | 45 +++++++ sdk/main/src/TransactionReceipt.cc | 23 +++- sdk/main/src/TransactionReceiptQuery.cc | 18 ++- sdk/main/src/TransactionRecord.cc | 17 ++- 7 files changed, 254 insertions(+), 10 deletions(-) create mode 100644 sdk/examples/AutoCreateAccountTransferTransactionExample.cc diff --git a/sdk/examples/AutoCreateAccountTransferTransactionExample.cc b/sdk/examples/AutoCreateAccountTransferTransactionExample.cc new file mode 100644 index 000000000..9655bc96b --- /dev/null +++ b/sdk/examples/AutoCreateAccountTransferTransactionExample.cc @@ -0,0 +1,126 @@ +/*- + * + * Hedera C++ SDK + * + * Copyright (C) 2020 - 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "AccountId.h" +#include "Client.h" +#include "ECDSAsecp256k1PrivateKey.h" +#include "ECDSAsecp256k1PublicKey.h" +#include "ED25519PrivateKey.h" +#include "EvmAddress.h" +#include "Hbar.h" +#include "TransactionReceipt.h" +#include "TransactionReceiptQuery.h" +#include "TransactionResponse.h" +#include "TransferTransaction.h" + +#include +#include + +using namespace Hedera; + +int main(int argc, char** argv) +{ + if (argc < 3) + { + std::cout << "Please input account ID and private key" << std::endl; + return 1; + } + + const AccountId operatorAccountId = AccountId::fromString(argv[1]); + const std::shared_ptr operatorPrivateKey = ED25519PrivateKey::fromString(argv[2]); + + /** + * Auto-create a new account using a public-address via a `TransferTransaction`. Reference: [HIP-583 Expand alias + * support in CryptoCreate & CryptoTransfer Transactions](https://hips.hedera.com/hip/hip-583) + * + * - Create an ECSDA private key. + * - Extract the ECDSA public key. + * - Extract the Ethereum public address. + * - Use the `TransferTransaction`. + * - Populate the `FromAddress` with the sender Hedera account ID. + * - Populate the `ToAddress` with Ethereum public address. + * - Note: Can transfer from public address to public address in the `TransferTransaction` for complete accounts. + * Transfers from hollow accounts will not work because the hollow account does not have a public key + * assigned to authorize transfers out of the account. + * - Sign the `TransferTransaction` transaction using an existing Hedera account and key paying for the transaction + * fee. + * - The `AccountCreateTransaction` is executed as a child transaction triggered by the `TransferTransaction`. + * - The Hedera account that was created has a public address the user specified in the TransferTransaction ToAddress. + * - Will not have a public key at this stage. + * - Cannot do anything besides receive tokens or hbars. + * - The alias property of the account does not have the public address. + * - Referred to as a hollow account. + * - To get the new account ID ask for the child receipts or child records for the parent transaction ID of the + * `TransferTransaction`. + * - Get the `AccountInfo` and verify the account is a hollow account with the supplied public address (may need to + * verify with mirror node API). + * - To enhance the hollow account to have a public key the hollow account needs to be specified as a transaction fee + * payer in a HAPI transaction. + * - Create a HAPI transaction and assign the new hollow account as the transaction fee payer. + * - Sign with the private key that corresponds to the public key on the hollow account. + * - Get the `AccountInfo` for the account and return the public key on the account to show it is a complete account. + */ + + // Get a client for the Hedera testnet, and set the operator account ID and key such that all generated transactions + // will be paid for by this account and be signed by this key. + Client client = Client::forTestnet(); + client.setOperator(operatorAccountId, operatorPrivateKey.get()); + + /** + * Step 1: Create an ECSDA private key. + */ + const std::unique_ptr privateKey = ECDSAsecp256k1PrivateKey::generatePrivateKey(); + + /** + * Step 2: Extract the ECDSA public key. + */ + const std::shared_ptr publicKey = + std::dynamic_pointer_cast(privateKey->getPublicKey()); + + /** + * Step 3: Extract the Ethereum public address. + */ + const EvmAddress evmAddress = publicKey->toEvmAddress(); + + /** + * Step 4: Use the `TransferTransaction` and set the EVM address field to the Ethereum public address + */ + TransferTransaction transferTransaction = TransferTransaction() + .addHbarTransfer(operatorAccountId, Hbar(10LL).negated()) + .addHbarTransfer(AccountId::fromEvmAddress(evmAddress), Hbar(10LL)) + .freezeWith(&client); + + /** + * Step 5: Sign the `TransferTransaction` transaction using an existing Hedera account and key paying for the + * transaction fee. + */ + const TransactionResponse response = transferTransaction.execute(client); + + /** + * Step 6: To get the new account ID, ask for the child receipts or child records for the parent transaction ID of the + * `TransferTransaction` (the `AccountCreateTransaction` is executed as a child transaction triggered by the + * `TransferTransaction`). + */ + const TransactionReceipt receipt = + TransactionReceiptQuery().setTransactionId(response.getTransactionId()).setIncludeChildren(true).execute(client); + + const AccountId newAccountId = receipt.mChildren.at(0).mAccountId.value(); + + return 0; +} diff --git a/sdk/examples/CMakeLists.txt b/sdk/examples/CMakeLists.txt index a0b2b624a..ea7a5dfd6 100644 --- a/sdk/examples/CMakeLists.txt +++ b/sdk/examples/CMakeLists.txt @@ -2,6 +2,7 @@ set(ACCOUNT_ALIAS_EXAMPLE_NAME ${PROJECT_NAME}-account-alias-example) set(ACCOUNT_ALLOWANCE_EXAMPLE_NAME ${PROJECT_NAME}-account-allowance-example) set(ACCOUNT_CREATE_WITH_HTS_EXAMPLE_NAME ${PROJECT_NAME}-account-create-with-hts-example) set(ACCOUNT_CREATION_WAYS_EXAMPLE_NAME ${PROJECT_NAME}-account-creation-ways-example) +set(AUTO_CREATE_ACCOUNT_TRANSFER_TRANSACTION_EXAMPLE_NAME ${PROJECT_NAME}-auto-create-account-transfer-transaction-example) set(CONSENSUS_PUB_SUB_EXAMPLE_NAME ${PROJECT_NAME}-consensus-pub-sub-example) set(CREATE_ACCOUNT_EXAMPLE_NAME ${PROJECT_NAME}-create-account-example) set(CREATE_SIMPLE_CONTRACT_EXAMPLE_NAME ${PROJECT_NAME}-create-simple-contract-example) @@ -30,6 +31,7 @@ add_executable(${ACCOUNT_ALIAS_EXAMPLE_NAME} AccountAliasExample.cc) add_executable(${ACCOUNT_ALLOWANCE_EXAMPLE_NAME} AccountAllowanceExample.cc) add_executable(${ACCOUNT_CREATE_WITH_HTS_EXAMPLE_NAME} AccountCreateWithHtsExample.cc) add_executable(${ACCOUNT_CREATION_WAYS_EXAMPLE_NAME} AccountCreationWaysExample.cc) +add_executable(${AUTO_CREATE_ACCOUNT_TRANSFER_TRANSACTION_EXAMPLE_NAME} AutoCreateAccountTransferTransactionExample.cc) add_executable(${CONSENSUS_PUB_SUB_EXAMPLE_NAME} ConsensusPubSubExample.cc) add_executable(${CREATE_ACCOUNT_EXAMPLE_NAME} CreateAccountExample.cc) add_executable(${CREATE_SIMPLE_CONTRACT_EXAMPLE_NAME} CreateSimpleContractExample.cc) @@ -76,6 +78,7 @@ target_link_libraries(${ACCOUNT_ALIAS_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${ACCOUNT_ALLOWANCE_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${ACCOUNT_CREATE_WITH_HTS_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${ACCOUNT_CREATION_WAYS_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) +target_link_libraries(${AUTO_CREATE_ACCOUNT_TRANSFER_TRANSACTION_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${CONSENSUS_PUB_SUB_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${CREATE_ACCOUNT_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) target_link_libraries(${CREATE_SIMPLE_CONTRACT_EXAMPLE_NAME} PUBLIC ${PROJECT_NAME}) @@ -107,6 +110,7 @@ install(TARGETS ${ACCOUNT_ALLOWANCE_EXAMPLE_NAME} ${ACCOUNT_CREATE_WITH_HTS_EXAMPLE_NAME} ${ACCOUNT_CREATION_WAYS_EXAMPLE_NAME} + ${AUTO_CREATE_ACCOUNT_TRANSFER_TRANSACTION_EXAMPLE_NAME} ${CONSENSUS_PUB_SUB_EXAMPLE_NAME} ${CREATE_ACCOUNT_EXAMPLE_NAME} ${CREATE_SIMPLE_CONTRACT_EXAMPLE_NAME} diff --git a/sdk/main/include/TransactionReceipt.h b/sdk/main/include/TransactionReceipt.h index 03ad86fa8..7eff25978 100644 --- a/sdk/main/include/TransactionReceipt.h +++ b/sdk/main/include/TransactionReceipt.h @@ -36,6 +36,7 @@ namespace proto { +class TransactionGetReceiptResponse; class TransactionReceipt; } @@ -48,13 +49,23 @@ namespace Hedera class TransactionReceipt { public: + /** + * Construct a TransactionReceipt object from a TransactionGetReceiptResponse protobuf object. + * + * @param proto The TransactionGetReceiptResponse protobuf object from which to construct a TransactionReceipt object. + * @return The constructed TransactionReceipt object. + */ + [[nodiscard]] static TransactionReceipt fromProtobuf(const proto::TransactionGetReceiptResponse& proto); + /** * Construct a TransactionReceipt object from a TransactionReceipt protobuf object. * - * @param proto The TransactionReceipt protobuf object from which to construct an TransactionReceipt object. + * @param proto The TransactionReceipt protobuf object from which to construct a TransactionReceipt object. + * @param transactionId The ID of the transaction to which the constructed TransactionReceipt will correspond. * @return The constructed TransactionReceipt object. */ - [[nodiscard]] static TransactionReceipt fromProtobuf(const proto::TransactionReceipt& proto); + [[nodiscard]] static TransactionReceipt fromProtobuf(const proto::TransactionReceipt& proto, + const TransactionId& transactionId = TransactionId()); /** * Validate the status and throw if it is not a Status::SUCCESS. @@ -63,6 +74,11 @@ class TransactionReceipt */ void validateStatus() const; + /** + * The ID of the transaction to which this TransactionReceipt corresponds. + */ + TransactionId mTransactionId; + /** * The consensus status of the transaction; is UNKNOWN if consensus has not been reached, or if the associated * transaction did not have a valid payer signature. @@ -181,6 +197,17 @@ class TransactionReceipt * newly-created NFTs. */ std::vector mSerialNumbers; + + /** + * The receipts of processing all transactions with the given ID, in consensus time order. + */ + std::vector mDuplicates; + + /** + * The receipts (if any) of all child transactions spawned by the transaction with the given top-level id, in + * consensus order. Always empty if the top-level status is UNKNOWN. + */ + std::vector mChildren; }; } // namespace Hedera diff --git a/sdk/main/include/TransactionReceiptQuery.h b/sdk/main/include/TransactionReceiptQuery.h index 93fa5460b..9ea663fec 100644 --- a/sdk/main/include/TransactionReceiptQuery.h +++ b/sdk/main/include/TransactionReceiptQuery.h @@ -49,6 +49,25 @@ class TransactionReceiptQuery : public Query getTransactionId() const { return mTransactionId; } + /** + * Get the child transaction retrieval policy for this TransactionReceiptQuery. + * + * @return \c TRUE if this TransactionReceiptQuery is currently configured to get the receipts of any child + * transactions, otherwise \c FALSE. + */ + [[nodiscard]] inline bool getIncludeChildren() const { return mIncludeChildren; } + + /** + * Get the duplicate transaction retrieval policy for this TransactionReceiptQuery. + * + * @return \c TRUE if this TransactionReceiptQuery is currently configured to get the receipts of any duplicate + * transactions, otherwise \c FALSE. + */ + [[nodiscard]] inline bool getIncludeDuplicates() const { return mIncludeDuplicates; } + private: /** * Derived from Executable. Construct a Query protobuf object from this TransactionReceiptQuery object. @@ -118,6 +153,16 @@ class TransactionReceiptQuery : public Query mTransactionId; + + /** + * Should the receipts of any children transactions be retrieved as well? + */ + bool mIncludeChildren = false; + + /** + * Should the receipts of any duplicates transactions be retrieved as well? + */ + bool mIncludeDuplicates = false; }; } // namespace Hedera diff --git a/sdk/main/src/TransactionReceipt.cc b/sdk/main/src/TransactionReceipt.cc index 899ed676f..2f178b2f1 100644 --- a/sdk/main/src/TransactionReceipt.cc +++ b/sdk/main/src/TransactionReceipt.cc @@ -21,14 +21,35 @@ #include "exceptions/ReceiptStatusException.h" #include "impl/Utilities.h" +#include #include namespace Hedera { //----- -TransactionReceipt TransactionReceipt::fromProtobuf(const proto::TransactionReceipt& proto) +TransactionReceipt TransactionReceipt::fromProtobuf(const proto::TransactionGetReceiptResponse& proto) +{ + TransactionReceipt receipt = TransactionReceipt::fromProtobuf(proto.receipt()); + + for (int i = 0; i < proto.duplicatetransactionreceipts_size(); ++i) + { + receipt.mDuplicates.push_back(TransactionReceipt::fromProtobuf(proto.duplicatetransactionreceipts(i))); + } + + for (int i = 0; i < proto.child_transaction_receipts_size(); ++i) + { + receipt.mChildren.push_back(TransactionReceipt::fromProtobuf(proto.child_transaction_receipts(i))); + } + + return receipt; +} + +//----- +TransactionReceipt TransactionReceipt::fromProtobuf(const proto::TransactionReceipt& proto, + const TransactionId& transactionId) { TransactionReceipt receipt; + receipt.mTransactionId = transactionId; receipt.mStatus = gProtobufResponseCodeToStatus.at(proto.status()); if (proto.has_accountid()) diff --git a/sdk/main/src/TransactionReceiptQuery.cc b/sdk/main/src/TransactionReceiptQuery.cc index de7adb497..f737e6926 100644 --- a/sdk/main/src/TransactionReceiptQuery.cc +++ b/sdk/main/src/TransactionReceiptQuery.cc @@ -36,6 +36,20 @@ TransactionReceiptQuery& TransactionReceiptQuery::setTransactionId(const Transac return *this; } +//----- +TransactionReceiptQuery& TransactionReceiptQuery::setIncludeChildren(bool children) +{ + mIncludeChildren = children; + return *this; +} + +//----- +TransactionReceiptQuery& TransactionReceiptQuery::setIncludeDuplicates(bool duplicates) +{ + mIncludeDuplicates = duplicates; + return *this; +} + //----- proto::Query TransactionReceiptQuery::makeRequest(const Client&, const std::shared_ptr&) const { @@ -47,6 +61,8 @@ proto::Query TransactionReceiptQuery::makeRequest(const Client&, const std::shar // This is a free query, so no payment required getTransactionReceiptQuery->set_allocated_transactionid(mTransactionId->toProtobuf().release()); + getTransactionReceiptQuery->set_include_child_receipts(mIncludeChildren); + getTransactionReceiptQuery->set_includeduplicates(mIncludeDuplicates); return query; } @@ -54,7 +70,7 @@ proto::Query TransactionReceiptQuery::makeRequest(const Client&, const std::shar //----- TransactionReceipt TransactionReceiptQuery::mapResponse(const proto::Response& response) const { - return TransactionReceipt::fromProtobuf(response.transactiongetreceipt().receipt()); + return TransactionReceipt::fromProtobuf(response.transactiongetreceipt()); } //----- diff --git a/sdk/main/src/TransactionRecord.cc b/sdk/main/src/TransactionRecord.cc index e8434d1db..3407eaed7 100644 --- a/sdk/main/src/TransactionRecord.cc +++ b/sdk/main/src/TransactionRecord.cc @@ -30,7 +30,17 @@ TransactionRecord TransactionRecord::fromProtobuf(const proto::TransactionRecord { TransactionRecord transactionRecord; - if (proto.has_receipt()) + if (proto.has_transactionid()) + { + transactionRecord.mTransactionID = TransactionId::fromProtobuf(proto.transactionid()); + + if (proto.has_receipt()) + { + transactionRecord.mReceipt = + TransactionReceipt::fromProtobuf(proto.receipt(), transactionRecord.mTransactionID.value()); + } + } + else { transactionRecord.mReceipt = TransactionReceipt::fromProtobuf(proto.receipt()); } @@ -42,11 +52,6 @@ TransactionRecord TransactionRecord::fromProtobuf(const proto::TransactionRecord transactionRecord.mConsensusTimestamp = internal::TimestampConverter::fromProtobuf(proto.consensustimestamp()); } - if (proto.has_transactionid()) - { - transactionRecord.mTransactionID = TransactionId::fromProtobuf(proto.transactionid()); - } - transactionRecord.mMemo = proto.memo(); transactionRecord.mTransactionFee = proto.transactionfee();