Skip to content

Commit

Permalink
Add AutoCreateAccountTransferTransactionExample (#495)
Browse files Browse the repository at this point in the history
Signed-off-by: Rob Walworth <[email protected]>
Signed-off-by: Deyan Zhekov <[email protected]>
Co-authored-by: Deyan Zhekov <[email protected]>
  • Loading branch information
rwalworth and deyanzz authored Sep 5, 2023
1 parent c5321f8 commit 8ef1414
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 10 deletions.
126 changes: 126 additions & 0 deletions sdk/examples/AutoCreateAccountTransferTransactionExample.cc
Original file line number Diff line number Diff line change
@@ -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 <iostream>
#include <memory>

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<ED25519PrivateKey> 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<ECDSAsecp256k1PrivateKey> privateKey = ECDSAsecp256k1PrivateKey::generatePrivateKey();

/**
* Step 2: Extract the ECDSA public key.
*/
const std::shared_ptr<ECDSAsecp256k1PublicKey> publicKey =
std::dynamic_pointer_cast<ECDSAsecp256k1PublicKey>(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;
}
4 changes: 4 additions & 0 deletions sdk/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -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}
Expand Down
31 changes: 29 additions & 2 deletions sdk/main/include/TransactionReceipt.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

namespace proto
{
class TransactionGetReceiptResponse;
class TransactionReceipt;
}

Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -181,6 +197,17 @@ class TransactionReceipt
* newly-created NFTs.
*/
std::vector<uint64_t> mSerialNumbers;

/**
* The receipts of processing all transactions with the given ID, in consensus time order.
*/
std::vector<TransactionReceipt> 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<TransactionReceipt> mChildren;
};

} // namespace Hedera
Expand Down
45 changes: 45 additions & 0 deletions sdk/main/include/TransactionReceiptQuery.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ class TransactionReceiptQuery : public Query<TransactionReceiptQuery, Transactio
*/
TransactionReceiptQuery& setTransactionId(const TransactionId& transactionId);

/**
* Set the child transaction retrieval policy for this TransactionReceiptQuery.
*
* @param children \c TRUE if this TransactionReceiptQuery should get the receipts of any child transactions,
* otherwise \c FALSE.
* @return A reference to this TransactionReceiptQuery object with the newly-set child transaction retrieval policy.
*/
TransactionReceiptQuery& setIncludeChildren(bool children);

/**
* Set the duplicate transaction retrieval policy for this TransactionReceiptQuery.
*
* @param duplicates \c TRUE if this TransactionReceiptQuery should get the receipts of any duplicate transactions,
* otherwise \c FALSE.
* @return A reference to this TransactionReceiptQuery object with the newly-set duplicate transaction retrieval
* policy.
*/
TransactionReceiptQuery& setIncludeDuplicates(bool duplicates);

/**
* Get the ID of the transaction of which this query is currently configured to get the receipt.
*
Expand All @@ -57,6 +76,22 @@ class TransactionReceiptQuery : public Query<TransactionReceiptQuery, Transactio
*/
[[nodiscard]] inline std::optional<TransactionId> 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.
Expand Down Expand Up @@ -118,6 +153,16 @@ class TransactionReceiptQuery : public Query<TransactionReceiptQuery, Transactio
* The ID of the transaction of which this query should get the receipt.
*/
std::optional<TransactionId> 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
Expand Down
23 changes: 22 additions & 1 deletion sdk/main/src/TransactionReceipt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,35 @@
#include "exceptions/ReceiptStatusException.h"
#include "impl/Utilities.h"

#include <proto/transaction_get_receipt.pb.h>
#include <proto/transaction_receipt.pb.h>

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())
Expand Down
18 changes: 17 additions & 1 deletion sdk/main/src/TransactionReceiptQuery.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<internal::Node>&) const
{
Expand All @@ -47,14 +61,16 @@ 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;
}

//-----
TransactionReceipt TransactionReceiptQuery::mapResponse(const proto::Response& response) const
{
return TransactionReceipt::fromProtobuf(response.transactiongetreceipt().receipt());
return TransactionReceipt::fromProtobuf(response.transactiongetreceipt());
}

//-----
Expand Down
17 changes: 11 additions & 6 deletions sdk/main/src/TransactionRecord.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand All @@ -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();

Expand Down

0 comments on commit 8ef1414

Please sign in to comment.