diff --git a/HieroApi.cmake b/HieroApi.cmake index 8fca27410..5fda857c1 100644 --- a/HieroApi.cmake +++ b/HieroApi.cmake @@ -1,8 +1,8 @@ -set(HAPI_VERSION_TAG "v0.69.1" CACHE STRING "Use the configured version tag for the Hiero API protobufs") -set(HAPI_COMMIT_HASH "83fa1eb1446524aa695fa2c0d817361805afb979" CACHE STRING "Use the configured commit hash for the Hiero API protobufs (overrides version tag if provided)") +set(HAPI_VERSION_TAG "v0.72.0-alpha.1" CACHE STRING "Use the configured version tag for the Hiero API protobufs") +set(HAPI_COMMIT_HASH "df70b5b7f32f0dabf1f6883c5d255248a8f3aa03" CACHE STRING "Use the configured commit hash for the Hiero API protobufs (overrides version tag if provided)") if (HAPI_VERSION_TAG STREQUAL "") - set(HAPI_VERSION_TAG "v0.69.1") + set(HAPI_VERSION_TAG "v0.72.0-alpha.1") endif () # Use commit hash if provided, otherwise use version tag diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index c4e907af4..003ae7679 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -58,8 +58,8 @@ set(PROTO_FILES services/get_by_key.proto services/get_by_solidity_id.proto services/hook_dispatch.proto + services/hook_store.proto services/hook_types.proto - services/lambda_sstore.proto services/network_get_execution_time.proto services/network_get_version_info.proto services/network_service.proto diff --git a/src/sdk/main/CMakeLists.txt b/src/sdk/main/CMakeLists.txt index ca89ad4cf..b99fc6bee 100644 --- a/src/sdk/main/CMakeLists.txt +++ b/src/sdk/main/CMakeLists.txt @@ -81,6 +81,7 @@ add_library(${PROJECT_NAME} STATIC src/FeeComponents.cc src/FeeData.cc src/FeeDataType.cc + src/FeeEstimateQuery.cc src/FeeSchedule.cc src/FeeSchedules.cc src/FileAppendTransaction.cc diff --git a/src/sdk/main/include/FeeEstimateMode.h b/src/sdk/main/include/FeeEstimateMode.h new file mode 100644 index 000000000..10899416a --- /dev/null +++ b/src/sdk/main/include/FeeEstimateMode.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +#ifndef HIERO_SDK_CPP_FEE_ESTIMATE_MODE_H_ +#define HIERO_SDK_CPP_FEE_ESTIMATE_MODE_H_ + +#include +#include + +namespace Hiero +{ +/** + * Enum class representing the fee estimation mode. + */ +enum class FeeEstimateMode +{ + /** + * STATE mode - Uses the current state of the network for fee estimation. + */ + STATE, + + /** + * TRANSIENT mode - Uses a transient simulation for fee estimation. + */ + TRANSIENT +}; + +/** + * Map of FeeEstimateMode to its corresponding string representation. + */ +[[maybe_unused]] static const std::unordered_map gFeeEstimateModeToString = { + {FeeEstimateMode::STATE, "STATE" }, + { FeeEstimateMode::TRANSIENT, "TRANSIENT"} +}; + +} // namespace Hiero + +#endif // HIERO_SDK_CPP_FEE_ESTIMATE_MODE_H_ diff --git a/src/sdk/main/include/FeeEstimateQuery.h b/src/sdk/main/include/FeeEstimateQuery.h new file mode 100644 index 000000000..55ef4c23c --- /dev/null +++ b/src/sdk/main/include/FeeEstimateQuery.h @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +#ifndef HIERO_SDK_CPP_FEE_ESTIMATE_QUERY_H_ +#define HIERO_SDK_CPP_FEE_ESTIMATE_QUERY_H_ + +#include "Defaults.h" +#include "FeeEstimateMode.h" +#include "FeeEstimateResponse.h" +#include "WrappedTransaction.h" + +#include +#include +#include + +namespace proto +{ +class Transaction; +} + +namespace Hiero +{ +class Client; + +/** + * FeeEstimateQuery allows users to query expected transaction fees without submitting transactions to the network. + */ +class FeeEstimateQuery +{ +public: + /** + * Default constructor. + */ + FeeEstimateQuery() = default; + + /** + * Execute the fee estimation query with the provided client. + * + * @param client The Client to use for the query. + * @return The FeeEstimateResponse containing the fee estimates. + * @throws std::invalid_argument If client is nullptr or no transaction is set. + * @throws IllegalStateException If the mirror node is not set or an error occurs during the query. + */ + [[nodiscard]] FeeEstimateResponse execute(const Client& client); + + /** + * Set the estimation mode (optional, defaults to STATE). + * + * @param mode The FeeEstimateMode to use. + * @return A reference to this FeeEstimateQuery. + */ + FeeEstimateQuery& setMode(FeeEstimateMode mode); + + /** + * Get the current estimation mode. + * + * @return The current FeeEstimateMode. + */ + [[nodiscard]] FeeEstimateMode getMode() const; + + /** + * Set the transaction to estimate (required). + * + * @param transaction The WrappedTransaction to estimate. + * @return A reference to this FeeEstimateQuery. + */ + FeeEstimateQuery& setTransaction(const WrappedTransaction& transaction); + + /** + * Get the current transaction. + * + * @return The current WrappedTransaction. + */ + [[nodiscard]] const WrappedTransaction& getTransaction() const; + + /** + * Set the maximum number of retry attempts. + * + * @param maxAttempts The maximum number of retry attempts. + * @return A reference to this FeeEstimateQuery. + */ + FeeEstimateQuery& setMaxAttempts(uint64_t maxAttempts); + + /** + * Get the maximum number of retry attempts. + * + * @return The maximum number of retry attempts. + */ + [[nodiscard]] uint64_t getMaxAttempts() const; + +private: + /** + * Call the fee estimate REST API endpoint. + * + * @param client The Client to use. + * @param protoTx The protobuf Transaction object. + * @return The FeeEstimateResponse. + */ + [[nodiscard]] FeeEstimateResponse callGetFeeEstimate(const Client& client, const proto::Transaction& protoTx); + + /** + * Estimate fees for a single transaction. + * + * @param client The Client to use. + * @return The FeeEstimateResponse. + */ + [[nodiscard]] FeeEstimateResponse estimateSingleTransaction(const Client& client); + + /** + * Execute chunked transaction fee estimation (for FileAppend or TopicMessageSubmit). + * + * @param client The Client to use. + * @return The aggregated FeeEstimateResponse. + */ + [[nodiscard]] FeeEstimateResponse executeChunkedTransaction(const Client& client); + + /** + * Determine if an error should trigger a retry. + * + * @param statusCode The HTTP status code. + * @return true if should retry, false otherwise. + */ + [[nodiscard]] bool shouldRetry(int statusCode) const; + + /** + * Build the mirror node REST API URL. + * + * @param client The Client to use. + * @return The URL string. + */ + [[nodiscard]] std::string buildMirrorNodeUrl(const Client& client) const; + + /** + * The estimation mode (defaults to STATE). + */ + FeeEstimateMode mMode = FeeEstimateMode::STATE; + + /** + * The transaction to estimate. + */ + WrappedTransaction mTransaction; + + /** + * The current retry attempt. + */ + uint64_t mAttempt = 0; + + /** + * The maximum number of retry attempts. + */ + uint64_t mMaxAttempts = DEFAULT_MAX_ATTEMPTS; +}; + +} // namespace Hiero + +#endif // HIERO_SDK_CPP_FEE_ESTIMATE_QUERY_H_ diff --git a/src/sdk/main/include/FeeEstimateResponse.h b/src/sdk/main/include/FeeEstimateResponse.h new file mode 100644 index 000000000..02b47e685 --- /dev/null +++ b/src/sdk/main/include/FeeEstimateResponse.h @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: Apache-2.0 +#ifndef HIERO_SDK_CPP_FEE_ESTIMATE_RESPONSE_H_ +#define HIERO_SDK_CPP_FEE_ESTIMATE_RESPONSE_H_ + +#include +#include +#include + +#include + +namespace Hiero +{ +/** + * Represents an extra fee component. + */ +struct FeeExtra +{ + /** + * The amount of the extra fee in tinybars. + */ + uint64_t mAmount = 0; + + /** + * The description of the extra fee. + */ + std::string mDescription; + + /** + * Construct from JSON. + * + * @param json The JSON object. + * @return The constructed FeeExtra. + */ + [[nodiscard]] static FeeExtra fromJson(const nlohmann::json& json) + { + FeeExtra extra; + if (json.contains("amount")) + { + extra.mAmount = json["amount"].get(); + } + if (json.contains("description")) + { + extra.mDescription = json["description"].get(); + } + return extra; + } +}; + +/** + * Represents a fee estimate with base fee and extras. + */ +struct FeeEstimate +{ + /** + * The base fee in tinybars. + */ + uint64_t mBase = 0; + + /** + * The list of extra fee components. + */ + std::vector mExtras; + + /** + * Calculate the subtotal of the fee (base + all extras). + * + * @return The subtotal fee in tinybars. + */ + [[nodiscard]] uint64_t subtotal() const + { + uint64_t total = mBase; + for (const auto& extra : mExtras) + { + total += extra.mAmount; + } + return total; + } + + /** + * Construct from JSON. + * + * @param json The JSON object. + * @return The constructed FeeEstimate. + */ + [[nodiscard]] static FeeEstimate fromJson(const nlohmann::json& json) + { + FeeEstimate estimate; + if (json.contains("base")) + { + estimate.mBase = json["base"].get(); + } + if (json.contains("extras") && json["extras"].is_array()) + { + for (const auto& extra : json["extras"]) + { + estimate.mExtras.push_back(FeeExtra::fromJson(extra)); + } + } + return estimate; + } +}; + +/** + * Represents the network fee component. + */ +struct NetworkFee +{ + /** + * The multiplier for the network fee. + */ + double mMultiplier = 0.0; + + /** + * The subtotal of the network fee in tinybars. + */ + uint64_t mSubtotal = 0; + + /** + * Construct from JSON. + * + * @param json The JSON object. + * @return The constructed NetworkFee. + */ + [[nodiscard]] static NetworkFee fromJson(const nlohmann::json& json) + { + NetworkFee networkFee; + if (json.contains("multiplier")) + { + networkFee.mMultiplier = json["multiplier"].get(); + } + if (json.contains("subtotal")) + { + networkFee.mSubtotal = json["subtotal"].get(); + } + return networkFee; + } +}; + +/** + * Represents the complete fee estimate response from the mirror node. + */ +struct FeeEstimateResponse +{ + /** + * The node fee estimate. + */ + FeeEstimate mNodeFee; + + /** + * The service fee estimate. + */ + FeeEstimate mServiceFee; + + /** + * The network fee. + */ + NetworkFee mNetworkFee; + + /** + * The total estimated fee in tinybars. + */ + uint64_t mTotal = 0; + + /** + * Notes or messages from the fee estimation. + */ + std::vector mNotes; + + /** + * Construct from JSON. + * + * @param json The JSON object. + * @return The constructed FeeEstimateResponse. + */ + [[nodiscard]] static FeeEstimateResponse fromJson(const nlohmann::json& json) + { + FeeEstimateResponse response; + + if (json.contains("nodeFee")) + { + response.mNodeFee = FeeEstimate::fromJson(json["nodeFee"]); + } + if (json.contains("serviceFee")) + { + response.mServiceFee = FeeEstimate::fromJson(json["serviceFee"]); + } + if (json.contains("networkFee")) + { + response.mNetworkFee = NetworkFee::fromJson(json["networkFee"]); + } + if (json.contains("total")) + { + response.mTotal = json["total"].get(); + } + if (json.contains("notes") && json["notes"].is_array()) + { + for (const auto& note : json["notes"]) + { + response.mNotes.push_back(note.get()); + } + } + + return response; + } +}; + +} // namespace Hiero + +#endif // HIERO_SDK_CPP_FEE_ESTIMATE_RESPONSE_H_ diff --git a/src/sdk/main/include/impl/HttpClient.h b/src/sdk/main/include/impl/HttpClient.h index 8f2705766..e0b1c7ead 100644 --- a/src/sdk/main/include/impl/HttpClient.h +++ b/src/sdk/main/include/impl/HttpClient.h @@ -26,11 +26,28 @@ namespace Hiero::internal::HttpClient * @param url The URL to which to submit the request. * @param httpMethod The HTTP method. * @param requestBody The HTTP request body. + * @param contentType The content type for POST requests (defaults to "application/json"). * @return The response data as a string. */ [[nodiscard]] std::string invokeREST(std::string_view url, std::string_view httpMethod = "GET", - std::string_view requestBody = ""); + std::string_view requestBody = "", + std::string_view contentType = "application/json"); + +/** + * Perform an HTTP request and return both the response body and status code. + * @param url The URL to which to submit the request. + * @param httpMethod The HTTP method. + * @param requestBody The HTTP request body. + * @param contentType The content type for POST requests. + * @param statusCode Output parameter for the HTTP status code. + * @return The response data as a string. + */ +[[nodiscard]] std::string invokeRESTWithStatus(std::string_view url, + std::string_view httpMethod, + std::string_view requestBody, + std::string_view contentType, + int& statusCode); } // namespace Hiero::internal diff --git a/src/sdk/main/src/FeeEstimateQuery.cc b/src/sdk/main/src/FeeEstimateQuery.cc new file mode 100644 index 000000000..c3f572c51 --- /dev/null +++ b/src/sdk/main/src/FeeEstimateQuery.cc @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "FeeEstimateQuery.h" +#include "Client.h" +#include "FileAppendTransaction.h" +#include "TopicMessageSubmitTransaction.h" +#include "exceptions/IllegalStateException.h" +#include "impl/HttpClient.h" +#include "impl/MirrorNetwork.h" +#include "impl/Utilities.h" + +#include +#include + +#include +#include + +namespace Hiero +{ +//----- +FeeEstimateResponse FeeEstimateQuery::execute(const Client& client) +{ + // Validate mirror network is set + const auto mirrorNetwork = client.getMirrorNetwork(); + if (mirrorNetwork.empty()) + { + throw IllegalStateException("Mirror network is not set on the client"); + } + + // Get the wrapped transaction and freeze it if needed + return estimateSingleTransaction(client); +} + +//----- +FeeEstimateQuery& FeeEstimateQuery::setMode(FeeEstimateMode mode) +{ + mMode = mode; + return *this; +} + +//----- +FeeEstimateMode FeeEstimateQuery::getMode() const +{ + return mMode; +} + +//----- +FeeEstimateQuery& FeeEstimateQuery::setTransaction(const WrappedTransaction& transaction) +{ + mTransaction = transaction; + return *this; +} + +//----- +const WrappedTransaction& FeeEstimateQuery::getTransaction() const +{ + return mTransaction; +} + +//----- +FeeEstimateQuery& FeeEstimateQuery::setMaxAttempts(uint64_t maxAttempts) +{ + mMaxAttempts = maxAttempts; + return *this; +} + +//----- +uint64_t FeeEstimateQuery::getMaxAttempts() const +{ + return mMaxAttempts; +} + +//----- +FeeEstimateResponse FeeEstimateQuery::callGetFeeEstimate(const Client& client, const proto::Transaction& protoTx) +{ + const std::string url = buildMirrorNodeUrl(client); + const std::string txBytes = protoTx.SerializeAsString(); + + std::string response; + int statusCode = 0; + std::string lastError; + + // Reset attempt counter for each call + mAttempt = 0; + + while (mAttempt < mMaxAttempts) + { + try + { + response = internal::HttpClient::invokeRESTWithStatus(url, "POST", txBytes, "application/protobuf", statusCode); + + if (statusCode == 200) + { + break; + } + + if (!shouldRetry(statusCode)) + { + throw IllegalStateException("Fee estimate API returned status " + std::to_string(statusCode) + ": " + response); + } + + lastError = "Received status " + std::to_string(statusCode); + } + catch (const std::exception& e) + { + lastError = e.what(); + if (!shouldRetry(statusCode)) + { + throw; + } + } + + // Calculate delay with exponential backoff: 250ms, 500ms, 1000ms, etc. + auto delayMs = static_cast(250.0 * static_cast(1ULL << mAttempt)); + if (delayMs > 8000) + { + delayMs = 8000; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(delayMs)); + ++mAttempt; + } + + if (statusCode != 200) + { + throw IllegalStateException("Failed to call fee estimate API after " + std::to_string(mMaxAttempts) + + " attempts: " + lastError); + } + + // Parse JSON response + try + { + const nlohmann::json jsonResponse = nlohmann::json::parse(response); + return FeeEstimateResponse::fromJson(jsonResponse); + } + catch (const nlohmann::json::exception& e) + { + throw IllegalStateException(std::string("Failed to parse fee estimate response: ") + e.what()); + } +} + +//----- +FeeEstimateResponse FeeEstimateQuery::estimateSingleTransaction(const Client& client) +{ + // Build the protobuf Transaction from the WrappedTransaction + std::unique_ptr protoTx = mTransaction.toProtobufTransaction(); + + if (!protoTx) + { + throw IllegalStateException("Failed to build protobuf transaction"); + } + + return callGetFeeEstimate(client, *protoTx); +} + +//----- +FeeEstimateResponse FeeEstimateQuery::executeChunkedTransaction(const Client& client) +{ + // For chunked transactions, we need to estimate each chunk and aggregate the results + // This is a simplified implementation - the full implementation would need to handle + // FileAppendTransaction and TopicMessageSubmitTransaction separately + + FeeEstimateResponse aggregatedResponse; + aggregatedResponse.mNodeFee = FeeEstimate{}; + aggregatedResponse.mServiceFee = FeeEstimate{}; + aggregatedResponse.mNetworkFee = NetworkFee{}; + aggregatedResponse.mNotes = {}; + + // For now, just estimate as a single transaction + // A full implementation would iterate through chunks + return estimateSingleTransaction(client); +} + +//----- +bool FeeEstimateQuery::shouldRetry(int statusCode) const +{ + // Retry on server errors (5xx) or rate limiting (429) + if (statusCode >= 500 || statusCode == 429) + { + return true; + } + + // Don't retry on client errors (4xx except 429) + if (statusCode >= 400 && statusCode < 500) + { + return false; + } + + // Retry on connection errors (represented by -1 or 0) + if (statusCode <= 0) + { + return true; + } + + return false; +} + +//----- +std::string FeeEstimateQuery::buildMirrorNodeUrl(const Client& client) const +{ + const auto mirrorNetwork = client.getMirrorNetwork(); + if (mirrorNetwork.empty()) + { + throw IllegalStateException("Mirror network is not set"); + } + + std::string mirrorUrl = mirrorNetwork[0]; + + // Check if it's localhost + const bool isLocalHost = (mirrorUrl.find("localhost") != std::string::npos) || + (mirrorUrl.find("127.0.0.1") != std::string::npos); + + // Add http/https prefix if not present + if (mirrorUrl.compare(0, 7, "http://") != 0 && mirrorUrl.compare(0, 8, "https://") != 0) + { + if (isLocalHost) + { + mirrorUrl = "http://" + mirrorUrl; + } + else + { + mirrorUrl = "https://" + mirrorUrl; + } + } + + // For localhost, use port 8084 + if (isLocalHost) + { + // Replace port if present or append default port + const size_t portPos = mirrorUrl.rfind(':'); + if (portPos != std::string::npos && portPos > 7) + { + mirrorUrl = mirrorUrl.substr(0, portPos) + ":8084"; + } + } + + // Build the full URL with mode parameter + const std::string modeStr = gFeeEstimateModeToString.at(mMode); + return mirrorUrl + "/api/v1/network/fees?mode=" + modeStr; +} + +} // namespace Hiero diff --git a/src/sdk/main/src/impl/HttpClient.cc b/src/sdk/main/src/impl/HttpClient.cc index 866d7b8e2..42ca65670 100644 --- a/src/sdk/main/src/impl/HttpClient.cc +++ b/src/sdk/main/src/impl/HttpClient.cc @@ -17,12 +17,16 @@ const int SCHEME_END_INDEX = 8; // // Perform an HTTP request. // -// @param url The URL to which to send the request. -// @param method The HTTP method type of this request. -// @param body The body of the request. +// @param url The URL to which to send the request. +// @param method The HTTP method type of this request. +// @param body The body of the request. +// @param contentType The content type of the request. // @return The response of the request. // -[[nodiscard]] std::string performRequest(std::string_view url, std::string_view method, std::string_view body) +[[nodiscard]] std::string performRequest(std::string_view url, + std::string_view method, + std::string_view body, + std::string_view contentType = "application/json") { // Create an HTTP client to communicate with the given URL. httplib::Client client(std::string(url.substr(0, url.find('/', SCHEME_END_INDEX)))); @@ -37,7 +41,7 @@ const int SCHEME_END_INDEX = 8; } else if (method == "POST") { - res = client.Post(path, body.data(), body.size(), "application/json"); + res = client.Post(path, body.data(), body.size(), contentType.data()); } else { @@ -52,6 +56,52 @@ const int SCHEME_END_INDEX = 8; return res->body; } +// +// Perform an HTTP request and return the status code. +// +// @param url The URL to which to send the request. +// @param method The HTTP method type of this request. +// @param body The body of the request. +// @param contentType The content type of the request. +// @param statusCode Output parameter for the HTTP status code. +// @return The response of the request. +// +[[nodiscard]] std::string performRequestWithStatus(std::string_view url, + std::string_view method, + std::string_view body, + std::string_view contentType, + int& statusCode) +{ + // Create an HTTP client to communicate with the given URL. + httplib::Client client(std::string(url.substr(0, url.find('/', SCHEME_END_INDEX)))); + const std::string path = url.substr(url.find('/', SCHEME_END_INDEX)).data(); + + httplib::Result res; + + // Perform the request based on the HTTP method + if (method == "GET") + { + res = client.Get(path); + } + else if (method == "POST") + { + res = client.Post(path, body.data(), body.size(), contentType.data()); + } + else + { + throw std::invalid_argument(std::string("Unsupported HTTP method: ") + method.data()); + } + + if (!res) + { + statusCode = -1; + throw std::runtime_error("HTTP error: " + httplib::to_string(res.error())); + } + + statusCode = res->status; + return res->body; +} + } // namespace // example mirrorNode query: @@ -59,9 +109,22 @@ const int SCHEME_END_INDEX = 8; // note: should time out before calling this function because the mirror node is not updated on time if accountID has // been created exactly before the call. Works without timeout if the data in the mirror node is there from some seconds // beforehand -std::string HttpClient::invokeREST(std::string_view url, std::string_view httpMethod, std::string_view requestBody) +std::string HttpClient::invokeREST(std::string_view url, + std::string_view httpMethod, + std::string_view requestBody, + std::string_view contentType) +{ + return performRequest(url, httpMethod, requestBody, contentType); +} + +//----- +std::string HttpClient::invokeRESTWithStatus(std::string_view url, + std::string_view httpMethod, + std::string_view requestBody, + std::string_view contentType, + int& statusCode) { - return performRequest(url, httpMethod, requestBody); + return performRequestWithStatus(url, httpMethod, requestBody, contentType, statusCode); } //----- diff --git a/src/sdk/tests/integration/CMakeLists.txt b/src/sdk/tests/integration/CMakeLists.txt index 95ddd8e54..dab540550 100644 --- a/src/sdk/tests/integration/CMakeLists.txt +++ b/src/sdk/tests/integration/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable(${TEST_PROJECT_NAME} FileDeleteTransactionIntegrationTests.cc FileInfoQueryIntegrationTests.cc FileUpdateTransactionIntegrationTests.cc + FeeEstimateQueryIntegrationTests.cc FreezeTransactionIntegrationTests.cc HIP1300IntegrationTests.cc HttpClientIntegrationTests.cc diff --git a/src/sdk/tests/integration/FeeEstimateQueryIntegrationTests.cc b/src/sdk/tests/integration/FeeEstimateQueryIntegrationTests.cc new file mode 100644 index 000000000..070b972cd --- /dev/null +++ b/src/sdk/tests/integration/FeeEstimateQueryIntegrationTests.cc @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +#include "AccountCreateTransaction.h" +#include "AccountDeleteTransaction.h" +#include "BaseIntegrationTest.h" +#include "Client.h" +#include "ED25519PrivateKey.h" +#include "FeeEstimateMode.h" +#include "FeeEstimateQuery.h" +#include "FeeEstimateResponse.h" +#include "Hbar.h" +#include "TransactionReceipt.h" +#include "TransactionResponse.h" +#include "TransferTransaction.h" +#include "WrappedTransaction.h" +#include "exceptions/IllegalStateException.h" + +#include + +using namespace Hiero; + +class FeeEstimateQueryIntegrationTests : public BaseIntegrationTest +{ +}; + +//----- +// Note: This test is disabled because the fee estimate REST API may not be available on all networks +TEST_F(FeeEstimateQueryIntegrationTests, DISABLED_EstimateTransferTransactionFee) +{ + // Given + const std::shared_ptr newAccountKey = ED25519PrivateKey::generatePrivateKey(); + + // Create a new account to transfer to + TransactionReceipt createReceipt; + ASSERT_NO_THROW(createReceipt = AccountCreateTransaction() + .setKeyWithoutAlias(newAccountKey->getPublicKey()) + .setInitialBalance(Hbar(1LL)) + .execute(getTestClient()) + .getReceipt(getTestClient())); + ASSERT_TRUE(createReceipt.mAccountId.has_value()); + const AccountId newAccountId = createReceipt.mAccountId.value(); + + // Create a transfer transaction to estimate + TransferTransaction transferTx = TransferTransaction() + .addHbarTransfer(getTestClient().getOperatorAccountId().value(), Hbar(-1LL)) + .addHbarTransfer(newAccountId, Hbar(1LL)); + + // Freeze the transaction + transferTx.freezeWith(&const_cast(getTestClient())); + + // Wrap the transaction + WrappedTransaction wrappedTx(transferTx); + + // When + FeeEstimateQuery query; + query.setTransaction(wrappedTx); + query.setMode(FeeEstimateMode::STATE); + + FeeEstimateResponse response; + EXPECT_NO_THROW(response = query.execute(getTestClient())); + + // Then + EXPECT_GT(response.mTotal, 0ULL); + + // Clean up - delete the created account + ASSERT_NO_THROW(AccountDeleteTransaction() + .setDeleteAccountId(newAccountId) + .setTransferAccountId(getTestClient().getOperatorAccountId().value()) + .freezeWith(&const_cast(getTestClient())) + .sign(newAccountKey) + .execute(getTestClient()) + .getReceipt(getTestClient())); +} + +//----- +// Note: This test is disabled because the fee estimate REST API may not be available on all networks +TEST_F(FeeEstimateQueryIntegrationTests, DISABLED_EstimateAccountCreateTransactionFee) +{ + // Given + const std::shared_ptr newAccountKey = ED25519PrivateKey::generatePrivateKey(); + + // Create an account create transaction to estimate + AccountCreateTransaction createTx = + AccountCreateTransaction().setKeyWithoutAlias(newAccountKey->getPublicKey()).setInitialBalance(Hbar(1LL)); + + // Freeze the transaction + createTx.freezeWith(&const_cast(getTestClient())); + + // Wrap the transaction + WrappedTransaction wrappedTx(createTx); + + // When + FeeEstimateQuery query; + query.setTransaction(wrappedTx); + query.setMode(FeeEstimateMode::STATE); + + FeeEstimateResponse response; + EXPECT_NO_THROW(response = query.execute(getTestClient())); + + // Then + EXPECT_GT(response.mTotal, 0ULL); +} + +//----- +TEST_F(FeeEstimateQueryIntegrationTests, GettersAndSetters) +{ + // Given + const std::shared_ptr newAccountKey = ED25519PrivateKey::generatePrivateKey(); + AccountCreateTransaction createTx = + AccountCreateTransaction().setKeyWithoutAlias(newAccountKey->getPublicKey()).setInitialBalance(Hbar(1LL)); + createTx.freezeWith(&const_cast(getTestClient())); + WrappedTransaction wrappedTx(createTx); + + // When + FeeEstimateQuery query; + query.setMode(FeeEstimateMode::TRANSIENT); + query.setMaxAttempts(5); + query.setTransaction(wrappedTx); + + // Then + EXPECT_EQ(query.getMode(), FeeEstimateMode::TRANSIENT); + EXPECT_EQ(query.getMaxAttempts(), 5ULL); +} + +//----- +TEST_F(FeeEstimateQueryIntegrationTests, DefaultMode) +{ + // Given / When + FeeEstimateQuery query; + + // Then + EXPECT_EQ(query.getMode(), FeeEstimateMode::STATE); +}