From 9ad091395c26857537e233fcc2b28b2a1bfa4d4d Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Mon, 27 Apr 2026 14:35:19 +0300 Subject: [PATCH 1/6] refactor: hip-1261 Signed-off-by: Mustafa Uzun --- .../sdk/examples/FeeEstimateQueryExample.java | 11 +--- .../hedera/hashgraph/sdk/FeeEstimateMode.java | 4 +- .../hashgraph/sdk/FeeEstimateQuery.java | 39 +++++++++++-- .../hashgraph/sdk/FeeEstimateResponse.java | 56 +++++++++---------- .../com/hedera/hashgraph/sdk/FeeExtra.java | 24 ++++---- .../sdk/FeeEstimateQueryMockTest.java | 53 +++++++++++++++++- .../FeeEstimateQueryIntegrationTest.java | 34 +++++++++-- 7 files changed, 153 insertions(+), 68 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java index 314bf0ded9..5a239919ee 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java @@ -110,7 +110,7 @@ private static FeeEstimateResponse estimateWithStateMode(Client client, Transfer printNodeFee(stateEstimate); printServiceFee(stateEstimate); printTotalFee(stateEstimate); - printNotes(stateEstimate); + System.out.println("\nHigh Volume Multiplier: " + stateEstimate.getHighVolumeMultiplier()); return stateEstimate; } @@ -148,15 +148,6 @@ private static void printTotalFee(FeeEstimateResponse estimate) { System.out.println("Total Estimated Fee: " + Hbar.fromTinybars(estimate.getTotal() / 100)); } - private static void printNotes(FeeEstimateResponse estimate) { - if (!estimate.getNotes().isEmpty()) { - System.out.println("\nNotes:"); - for (String note : estimate.getNotes()) { - System.out.println(" - " + note); - } - } - } - private static FeeEstimateResponse estimateWithIntrinsicMode(Client client, TransferTransaction tx) throws Exception { System.out.println("\n=== Estimating Fees with INTRINSIC Mode ==="); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateMode.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateMode.java index bec12a5f6d..1fca0c4b56 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateMode.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateMode.java @@ -8,7 +8,7 @@ */ public enum FeeEstimateMode { /** - * Default mode: uses latest known state. + * Uses latest known state. *

* This mode calculates fees based on the current state of the network, * taking into account all state-dependent factors such as current @@ -17,7 +17,7 @@ public enum FeeEstimateMode { STATE(0), /** - * Intrinsic mode: ignores state-dependent factors. + * Default mode: ignores state-dependent factors. *

* This mode calculates fees based only on the intrinsic properties of * the transaction itself, ignoring dynamic network conditions. This diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java index f2c2e859d2..d362c9c47b 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java @@ -30,6 +30,7 @@ public class FeeEstimateQuery { @Nullable private com.hedera.hashgraph.sdk.proto.Transaction transaction = null; + private int highVolumeThrottle = 0; private int maxAttempts = 10; private Duration maxBackoff = Duration.ofSeconds(8L); @@ -64,7 +65,7 @@ public FeeEstimateMode getMode() { /** * Set the mode for fee estimation. *

- * Defaults to {@link FeeEstimateMode#STATE} if not set. + * Defaults to {@link FeeEstimateMode#INTRINSIC} if not set. * * @param mode the fee estimate mode * @return {@code this} @@ -75,6 +76,32 @@ public FeeEstimateQuery setMode(FeeEstimateMode mode) { return this; } + /** + * Extract the high-volume throttle utilization in basis points. + * + * @return the high-volume throttle value (0–10000) + */ + public int getHighVolumeThrottle() { + return highVolumeThrottle; + } + + /** + * Set the high-volume throttle utilization in basis points (0–10000, where 10000 = 100%). + *

+ * When non-zero, the mirror node returns a high-volume pricing multiplier + * in the response. + * + * @param highVolumeThrottle the throttle utilization in basis points + * @return {@code this} + */ + public FeeEstimateQuery setHighVolumeThrottle(int highVolumeThrottle) { + if (highVolumeThrottle < 0 || highVolumeThrottle > 10000) { + throw new IllegalArgumentException("highVolumeThrottle must be between 0 and 10000"); + } + this.highVolumeThrottle = highVolumeThrottle; + return this; + } + /** * Extract the transaction to estimate fees for. * @@ -178,7 +205,7 @@ public FeeEstimateResponse execute(Client client) throws IOException, Interrupte * @throws InterruptedException if the operation is interrupted */ public FeeEstimateResponse execute(Client client, Duration timeout) throws IOException, InterruptedException { - var resolvedMode = mode != null ? mode : FeeEstimateMode.STATE; + var resolvedMode = mode != null ? mode : FeeEstimateMode.INTRINSIC; var requestPayload = getRequestPayload(); var url = buildUrl(client, resolvedMode); @@ -253,7 +280,7 @@ public CompletableFuture executeAsync(Client client) { * @return the fee estimate response */ public CompletableFuture executeAsync(Client client, Duration timeout) { - var resolvedMode = mode != null ? mode : FeeEstimateMode.STATE; + var resolvedMode = mode != null ? mode : FeeEstimateMode.INTRINSIC; CompletableFuture returnFuture = new CompletableFuture<>(); executeAsync(client, timeout, resolvedMode, returnFuture, 1); return returnFuture; @@ -352,7 +379,11 @@ private byte[] getRequestPayload() { private String buildUrl(Client client, FeeEstimateMode resolvedMode) { // Keep mode casing consistent with JS SDK (uppercase) - return client.getMirrorRestBaseUrl() + "/network/fees?mode=" + resolvedMode.toString(); + String url = client.getMirrorRestBaseUrl() + "/network/fees?mode=" + resolvedMode.toString(); + if (highVolumeThrottle > 0) { + url += "&high_volume_throttle=" + highVolumeThrottle; + } + return url; } private HttpRequest buildHttpRequest(String url, Duration timeout, byte[] payload) { diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateResponse.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateResponse.java index 5d646a301a..146f476edc 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateResponse.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateResponse.java @@ -4,9 +4,6 @@ import com.google.common.base.MoreObjects; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Objects; import javax.annotation.Nullable; @@ -46,11 +43,12 @@ public final class FeeEstimateResponse { private final FeeEstimate serviceFee; /** - * An array of strings for any caveats. + * The high-volume throttle multiplier returned by the mirror node. *

- * For example: ["Fallback to worst-case due to missing state"] + * When non-zero high-volume throttle utilization is requested, this value + * will be greater than or equal to 1. */ - private final List notes; + private final long highVolumeMultiplier; /** * The sum of the network, node, and service subtotals in tinycents. @@ -60,24 +58,24 @@ public final class FeeEstimateResponse { /** * Constructor. * - * @param mode the fee estimate mode used - * @param networkFee the network fee component - * @param nodeFee the node fee estimate - * @param notes the list of notes/caveats - * @param serviceFee the service fee estimate - * @param total the total fee in tinycents + * @param mode the fee estimate mode used + * @param networkFee the network fee component + * @param nodeFee the node fee estimate + * @param highVolumeMultiplier the high-volume throttle multiplier + * @param serviceFee the service fee estimate + * @param total the total fee in tinycents */ FeeEstimateResponse( FeeEstimateMode mode, @Nullable NetworkFee networkFee, @Nullable FeeEstimate nodeFee, - List notes, + long highVolumeMultiplier, @Nullable FeeEstimate serviceFee, long total) { this.mode = mode; this.networkFee = networkFee; this.nodeFee = nodeFee; - this.notes = Collections.unmodifiableList(new ArrayList<>(notes)); + this.highVolumeMultiplier = highVolumeMultiplier; this.serviceFee = serviceFee; this.total = total; } @@ -96,7 +94,7 @@ static FeeEstimateResponse fromJson(String json, FeeEstimateMode defaultMode) { parseModeFromJson(root, defaultMode), parseNetworkFeeFromJson(root), parseFeeEstimateFromJson(root, "node"), - parseNotesFromJson(root), + parseHighVolumeMultiplierFromJson(root), parseFeeEstimateFromJson(root, "service"), parseTotalFromJson(root)); } @@ -148,17 +146,15 @@ private static FeeEstimate parseFeeEstimateFromJson(JsonObject root, String fiel } /** - * Parse notes from JSON. + * Parse high-volume multiplier from JSON. * * @param root the JSON object - * @return the list of notes + * @return the high-volume multiplier value, or 0 if not present */ - private static List parseNotesFromJson(JsonObject root) { - List notes = new ArrayList<>(); - if (root.has("notes") && root.get("notes").isJsonArray()) { - root.getAsJsonArray("notes").forEach(element -> notes.add(element.getAsString())); - } - return notes; + private static long parseHighVolumeMultiplierFromJson(JsonObject root) { + return root.has("high_volume_multiplier") + ? root.get("high_volume_multiplier").getAsLong() + : 0L; } /** @@ -201,12 +197,12 @@ public FeeEstimate getNodeFee() { } /** - * Extract the list of notes/caveats. + * Extract the high-volume throttle multiplier. * - * @return an unmodifiable list of notes + * @return the high-volume multiplier */ - public List getNotes() { - return notes; + public long getHighVolumeMultiplier() { + return highVolumeMultiplier; } /** @@ -234,7 +230,7 @@ public String toString() { .add("mode", mode) .add("network", networkFee) .add("node", nodeFee) - .add("notes", notes) + .add("highVolumeMultiplier", highVolumeMultiplier) .add("service", serviceFee) .add("total", total) .toString(); @@ -250,14 +246,14 @@ public boolean equals(Object o) { } return total == that.total && mode == that.mode + && highVolumeMultiplier == that.highVolumeMultiplier && Objects.equals(networkFee, that.networkFee) && Objects.equals(nodeFee, that.nodeFee) - && Objects.equals(notes, that.notes) && Objects.equals(serviceFee, that.serviceFee); } @Override public int hashCode() { - return Objects.hash(mode, networkFee, nodeFee, notes, serviceFee, total); + return Objects.hash(mode, networkFee, nodeFee, highVolumeMultiplier, serviceFee, total); } } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeExtra.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeExtra.java index db26769909..1da82504a3 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeExtra.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeExtra.java @@ -15,12 +15,12 @@ public final class FeeExtra { /** * The charged count of items as calculated by max(0, count - included). */ - private final int charged; + private final long charged; /** * The actual count of items received. */ - private final int count; + private final long count; /** * The fee price per unit in tinycents. @@ -30,7 +30,7 @@ public final class FeeExtra { /** * The count of this "extra" that is included for free. */ - private final int included; + private final long included; /** * The unique name of this extra fee as defined in the fee schedule. @@ -55,7 +55,7 @@ public final class FeeExtra { * @param name the unique name of this extra fee * @param subtotal the subtotal in tinycents */ - FeeExtra(int charged, int count, long feePerUnit, int included, @Nullable String name, long subtotal) { + FeeExtra(long charged, long count, long feePerUnit, long included, @Nullable String name, long subtotal) { this.charged = charged; this.count = count; this.feePerUnit = feePerUnit; @@ -71,10 +71,10 @@ public final class FeeExtra { * @return the new FeeExtra */ static FeeExtra fromJson(com.google.gson.JsonObject feeExtra) { - int charged = getInt(feeExtra, "charged"); - int count = getInt(feeExtra, "count"); + long charged = getLong(feeExtra, "charged"); + long count = getLong(feeExtra, "count"); long feePerUnit = getLong(feeExtra, "fee_per_unit"); - int included = getInt(feeExtra, "included"); + long included = getLong(feeExtra, "included"); String name = feeExtra.has("name") && !feeExtra.get("name").isJsonNull() ? feeExtra.get("name").getAsString() : null; @@ -88,7 +88,7 @@ static FeeExtra fromJson(com.google.gson.JsonObject feeExtra) { * * @return the charged count of items */ - public int getCharged() { + public long getCharged() { return charged; } @@ -97,7 +97,7 @@ public int getCharged() { * * @return the actual count of items */ - public int getCount() { + public long getCount() { return count; } @@ -115,7 +115,7 @@ public long getFeePerUnit() { * * @return the count included for free */ - public int getIncluded() { + public long getIncluded() { return included; } @@ -171,10 +171,6 @@ public int hashCode() { return Objects.hash(charged, count, feePerUnit, included, name, subtotal); } - private static int getInt(com.google.gson.JsonObject object, String key) { - return object.get(key).getAsInt(); - } - private static long getLong(com.google.gson.JsonObject object, String key) { return object.get(key).getAsLong(); } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/FeeEstimateQueryMockTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/FeeEstimateQueryMockTest.java index c19a7c7594..1655ad8c0c 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/FeeEstimateQueryMockTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/FeeEstimateQueryMockTest.java @@ -94,6 +94,48 @@ void succeedsOnFirstAttempt() throws IOException, InterruptedException { assertThat(response.getMode()).isEqualTo(FeeEstimateMode.INTRINSIC); assertThat(response.getTotal()).isEqualTo(3 * 10 + 10 + 20); + assertThat(response.getHighVolumeMultiplier()).isEqualTo(1); + assertThat(stub.requestCount()).isEqualTo(1); + assertThat(stub.getLastQueryParams()).doesNotContain("high_volume_throttle"); + } + + @Test + @DisplayName("Given a FeeEstimateQuery without explicit mode, it defaults to INTRINSIC") + void defaultsToIntrinsic() throws IOException, InterruptedException { + query.setTransaction(DUMMY_TRANSACTION); + + stub.enqueue(new StubResponse(200, newSuccessResponse(FeeEstimateMode.INTRINSIC, 3, 10, 20))); + + var response = query.execute(client); + + assertThat(stub.getLastQueryParams()).contains("mode=INTRINSIC"); + assertThat(response.getMode()).isEqualTo(FeeEstimateMode.INTRINSIC); + } + + @Test + @DisplayName("Given a FeeEstimateQuery with high volume throttle, it sends the parameter in the URL") + void sendsHighVolumeThrottle() throws IOException, InterruptedException { + query.setTransaction(DUMMY_TRANSACTION).setHighVolumeThrottle(5000); + + stub.enqueue(new StubResponse(200, newSuccessResponse(FeeEstimateMode.INTRINSIC, 3, 10, 20))); + + var response = query.execute(client); + + assertThat(stub.getLastQueryParams()).contains("high_volume_throttle=5000"); + assertThat(response.getHighVolumeMultiplier()).isGreaterThanOrEqualTo(1); + } + + @Test + @DisplayName("Given a FeeEstimateQuery receives HTTP 400, it does not retry") + void doesNotRetryOn400() { + query.setTransaction(DUMMY_TRANSACTION).setMaxAttempts(3); + + stub.enqueue(new StubResponse(400, "bad request")); + + org.assertj.core.api.Assertions.assertThatThrownBy(() -> query.execute(client)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("400"); + assertThat(stub.requestCount()).isEqualTo(1); } @@ -107,11 +149,10 @@ private static String newSuccessResponse( "network": {"multiplier": %d, "subtotal": %d}, "node": {"base": %d, "extras": []}, "service": {"base": %d, "extras": []}, - "notes": [], + "high_volume_multiplier": 1, "total": %d } - """ - .formatted(mode, networkMultiplier, networkSubtotal, nodeBase, serviceBase, total); + """.formatted(mode, networkMultiplier, networkSubtotal, nodeBase, serviceBase, total); } private static final class StubResponse { @@ -127,6 +168,7 @@ private static final class StubResponse { private static final class StubMirrorRestServer { private final Queue responses = new ArrayDeque<>(); private int observedRequests = 0; + private String lastQueryParams; private HttpServer server; private int port; @@ -135,6 +177,7 @@ void start() throws IOException { port = server.getAddress().getPort(); server.createContext("/api/v1/network/fees", exchange -> { observedRequests++; + lastQueryParams = exchange.getRequestURI().getQuery(); var response = responses.poll(); assertThat(response) .as("response should be queued before invoking network fee estimation") @@ -176,6 +219,10 @@ int getPort() { return port; } + String getLastQueryParams() { + return lastQueryParams; + } + void verify() { assertThat(responses) .as("all queued responses should have been served") diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java index 1ba0894dbe..4458ed8912 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java @@ -107,8 +107,8 @@ void transferTransactionIntrinsicModeFeeEstimate() throws Throwable { @Test @DisplayName( - "Given a TransferTransaction without explicit mode, when fee estimate is requested, then STATE mode is used by default") - void transferTransactionDefaultModeIsState() throws Throwable { + "Given a TransferTransaction without explicit mode, when fee estimate is requested, then INTRINSIC mode is used by default") + void transferTransactionDefaultModeIsIntrinsic() throws Throwable { try (var testEnv = createFeeEstimateTestEnv()) { var transaction = new TransferTransaction() .addHbarTransfer(testEnv.operatorId, Hbar.fromTinybars(-1)) @@ -121,7 +121,31 @@ void transferTransactionDefaultModeIsState() throws Throwable { var response = new FeeEstimateQuery().setTransaction(transaction).execute(testEnv.client); assertFeeComponentsPresent(response); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.STATE); + assertThat(response.getMode()).isEqualTo(FeeEstimateMode.INTRINSIC); + assertComponentTotalsConsistent(response); + } + } + + @Test + @DisplayName( + "Given a TransferTransaction with high volume throttle, when fee estimate is requested, then high volume multiplier is returned") + void feeEstimateQueryWithHighVolumeThrottle() throws Throwable { + try (var testEnv = createFeeEstimateTestEnv()) { + var transaction = new TransferTransaction() + .addHbarTransfer(testEnv.operatorId, Hbar.fromTinybars(-1)) + .addHbarTransfer(AccountId.fromString("0.0.3"), Hbar.fromTinybars(1)) + .freezeWith(testEnv.client) + .signWithOperator(testEnv.client); + + waitForMirrorNodeSync(); + + var response = new FeeEstimateQuery() + .setTransaction(transaction) + .setHighVolumeThrottle(5000) + .execute(testEnv.client); + + assertFeeComponentsPresent(response); + assertThat(response.getHighVolumeMultiplier()).isGreaterThanOrEqualTo(1); assertComponentTotalsConsistent(response); } } @@ -382,8 +406,8 @@ private static void assertFeeComponentsPresent(FeeEstimateResponse response) { assertThat(response.getServiceFee().getBase()).isGreaterThanOrEqualTo(0); assertThat(response.getServiceFee().getExtras()).isNotNull(); - // Notes and total - assertThat(response.getNotes()).isNotNull(); + // High volume multiplier and total + assertThat(response.getHighVolumeMultiplier()).isGreaterThanOrEqualTo(1); assertThat(response.getTotal()).isGreaterThan(0); } From 85812c08d199d0833afb404b2713fa5b2e669297 Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Mon, 27 Apr 2026 15:25:56 +0300 Subject: [PATCH 2/6] chore: upgrade mirror node version Signed-off-by: Mustafa Uzun --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4cc354497..fb216f3f32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -179,7 +179,7 @@ jobs: uses: hiero-ledger/hiero-solo-action@4d42a74e8e644a2753f3bb7a2afa429305375b14 # v0.16 with: installMirrorNode: true - mirrorNodeVersion: v0.145.2 + mirrorNodeVersion: v0.152.0 hieroVersion: v0.70.0-rc.2 - name: Build SDK @@ -254,7 +254,7 @@ jobs: installMirrorNode: true dualMode: true hieroVersion: v0.68.0 - mirrorNodeVersion: v0.142.0 + mirrorNodeVersion: v0.152.0 - name: Build SDK run: ./gradlew assemble @@ -309,7 +309,7 @@ jobs: uses: hiero-ledger/hiero-solo-action@4d42a74e8e644a2753f3bb7a2afa429305375b14 # v0.16 with: installMirrorNode: true - mirrorNodeVersion: v0.145.2 + mirrorNodeVersion: v0.152.0 hieroVersion: v0.70.0-rc.2 - name: Build SDK From ad9eb67ea0b5fd11b5a9d82b79158d3daec25bea Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Wed, 29 Apr 2026 13:14:42 +0300 Subject: [PATCH 3/6] feat: add estimateFee Signed-off-by: Mustafa Uzun --- .../java/com/hedera/hashgraph/sdk/Transaction.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java index f52a0b8e8a..e09d638435 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java @@ -1594,6 +1594,20 @@ public String toString() { return body.buildPartial().toString().replaceAll("@[A-Za-z0-9]+", ""); } + /** + * Create a {@link FeeEstimateQuery} pre-populated with this transaction. + *

+ * This is a convenience method equivalent to: + *

{@code
+     * new FeeEstimateQuery().setTransaction(transaction)
+     * }
+ * + * @return a new FeeEstimateQuery with this transaction set + */ + public FeeEstimateQuery estimateFee() { + return new FeeEstimateQuery().setTransaction(this); + } + /** * This method retrieves the size of the transaction * @return From 505c6724bc6cd4abe6f0b671fb501dbb77190ecf Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Wed, 29 Apr 2026 13:58:39 +0300 Subject: [PATCH 4/6] refactor: variable names Signed-off-by: Mustafa Uzun --- .../sdk/examples/FeeEstimateQueryExample.java | 24 +++---- .../hashgraph/sdk/FeeEstimateQuery.java | 8 +-- .../hashgraph/sdk/FeeEstimateResponse.java | 72 +++++++------------ .../sdk/FeeEstimateQueryMockTest.java | 4 -- .../FeeEstimateQueryIntegrationTest.java | 26 +++---- 5 files changed, 54 insertions(+), 80 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java index 5a239919ee..3b86a5e35f 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java @@ -105,7 +105,6 @@ private static FeeEstimateResponse estimateWithStateMode(Client client, Transfer .setTransaction(tx) .execute(client); - System.out.println("Mode: " + stateEstimate.getMode()); printNetworkFee(stateEstimate); printNodeFee(stateEstimate); printServiceFee(stateEstimate); @@ -117,15 +116,15 @@ private static FeeEstimateResponse estimateWithStateMode(Client client, Transfer private static void printNetworkFee(FeeEstimateResponse estimate) { System.out.println("\nNetwork Fee:"); - System.out.println(" Multiplier: " + estimate.getNetworkFee().getMultiplier()); - System.out.println(" Subtotal: " + estimate.getNetworkFee().getSubtotal() + " tinycents"); + System.out.println(" Multiplier: " + estimate.getNetwork().getMultiplier()); + System.out.println(" Subtotal: " + estimate.getNetwork().getSubtotal() + " tinycents"); } private static void printNodeFee(FeeEstimateResponse estimate) { System.out.println("\nNode Fee:"); - System.out.println(" Base: " + estimate.getNodeFee().getBase() + " tinycents"); - long nodeTotal = estimate.getNodeFee().getBase(); - for (FeeExtra extra : estimate.getNodeFee().getExtras()) { + System.out.println(" Base: " + estimate.getNode().getBase() + " tinycents"); + long nodeTotal = estimate.getNode().getBase(); + for (FeeExtra extra : estimate.getNode().getExtras()) { System.out.println(" Extra - " + extra.getName() + ": " + extra.getSubtotal() + " tinycents"); nodeTotal += extra.getSubtotal(); } @@ -134,9 +133,9 @@ private static void printNodeFee(FeeEstimateResponse estimate) { private static void printServiceFee(FeeEstimateResponse estimate) { System.out.println("\nService Fee:"); - System.out.println(" Base: " + estimate.getServiceFee().getBase() + " tinycents"); - long serviceTotal = estimate.getServiceFee().getBase(); - for (FeeExtra extra : estimate.getServiceFee().getExtras()) { + System.out.println(" Base: " + estimate.getService().getBase() + " tinycents"); + long serviceTotal = estimate.getService().getBase(); + for (FeeExtra extra : estimate.getService().getExtras()) { System.out.println(" Extra - " + extra.getName() + ": " + extra.getSubtotal() + " tinycents"); serviceTotal += extra.getSubtotal(); } @@ -157,12 +156,11 @@ private static FeeEstimateResponse estimateWithIntrinsicMode(Client client, Tran .setTransaction(tx) .execute(client); - System.out.println("Mode: " + intrinsicEstimate.getMode()); System.out.println( - "Network Fee Subtotal: " + intrinsicEstimate.getNetworkFee().getSubtotal() + " tinycents"); - System.out.println("Node Fee Base: " + intrinsicEstimate.getNodeFee().getBase() + " tinycents"); + "Network Fee Subtotal: " + intrinsicEstimate.getNetwork().getSubtotal() + " tinycents"); + System.out.println("Node Fee Base: " + intrinsicEstimate.getNode().getBase() + " tinycents"); System.out.println( - "Service Fee Base: " + intrinsicEstimate.getServiceFee().getBase() + " tinycents"); + "Service Fee Base: " + intrinsicEstimate.getService().getBase() + " tinycents"); System.out.println("Total Estimated Fee: " + intrinsicEstimate.getTotal() + " tinycents"); System.out.println("Total Estimated Fee: " + Hbar.fromTinybars(intrinsicEstimate.getTotal() / 100)); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java index d362c9c47b..434f794662 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java @@ -214,7 +214,7 @@ public FeeEstimateResponse execute(Client client, Duration timeout) throws IOExc var response = HTTP_CLIENT.send( buildHttpRequest(url, timeout, requestPayload), HttpResponse.BodyHandlers.ofString()); - var result = handleResponse(response, resolvedMode, attempt); + var result = handleResponse(response, attempt); if (result != null) { return result; } @@ -231,9 +231,9 @@ public FeeEstimateResponse execute(Client client, Duration timeout) throws IOExc * Handle the HTTP response and return the result or null if retry is needed. */ private FeeEstimateResponse handleResponse( - HttpResponse response, FeeEstimateMode resolvedMode, int attempt) { + HttpResponse response, int attempt) { if (isSuccessfulResponse(response.statusCode())) { - return FeeEstimateResponse.fromJson(response.body(), resolvedMode); + return FeeEstimateResponse.fromJson(response.body()); } if (!shouldRetry(response.statusCode()) || attempt >= maxAttempts) { @@ -343,7 +343,7 @@ private void handleAsyncResponse( int attempt, HttpResponse response) { if (isSuccessfulResponse(response.statusCode())) { - returnFuture.complete(FeeEstimateResponse.fromJson(response.body(), resolvedMode)); + returnFuture.complete(FeeEstimateResponse.fromJson(response.body())); return; } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateResponse.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateResponse.java index 146f476edc..f6dcfd65a4 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateResponse.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateResponse.java @@ -14,17 +14,13 @@ * along with the total estimated cost in tinycents. */ public final class FeeEstimateResponse { - /** - * The mode that was used to calculate the fees. - */ - private final FeeEstimateMode mode; /** * The network fee component which covers the cost of gossip, consensus, * signature verifications, fee payment, and storage. */ @Nullable - private final NetworkFee networkFee; + private final NetworkFee network; /** * The node fee component which is to be paid to the node that submitted the @@ -33,14 +29,14 @@ public final class FeeEstimateResponse { * incentivizes the node to accept new transactions from users. */ @Nullable - private final FeeEstimate nodeFee; + private final FeeEstimate node; /** * The service fee component which covers execution costs, state saved in the * Merkle tree, and additional costs to the blockchain storage. */ @Nullable - private final FeeEstimate serviceFee; + private final FeeEstimate service; /** * The high-volume throttle multiplier returned by the mirror node. @@ -58,25 +54,22 @@ public final class FeeEstimateResponse { /** * Constructor. * - * @param mode the fee estimate mode used - * @param networkFee the network fee component - * @param nodeFee the node fee estimate + * @param network the network fee component + * @param node the node fee estimate * @param highVolumeMultiplier the high-volume throttle multiplier - * @param serviceFee the service fee estimate + * @param service the service fee estimate * @param total the total fee in tinycents */ FeeEstimateResponse( - FeeEstimateMode mode, - @Nullable NetworkFee networkFee, - @Nullable FeeEstimate nodeFee, + @Nullable NetworkFee network, + @Nullable FeeEstimate node, long highVolumeMultiplier, - @Nullable FeeEstimate serviceFee, + @Nullable FeeEstimate service, long total) { - this.mode = mode; - this.networkFee = networkFee; - this.nodeFee = nodeFee; + this.network = network; + this.node = node; this.highVolumeMultiplier = highVolumeMultiplier; - this.serviceFee = serviceFee; + this.service = service; this.total = total; } @@ -84,14 +77,12 @@ public final class FeeEstimateResponse { * Create a FeeEstimateResponse from a REST JSON payload. * * @param json the raw JSON response - * @param defaultMode the mode to fall back to when the response omits mode * @return the new FeeEstimateResponse */ - static FeeEstimateResponse fromJson(String json, FeeEstimateMode defaultMode) { + static FeeEstimateResponse fromJson(String json) { JsonObject root = JsonParser.parseString(json).getAsJsonObject(); return new FeeEstimateResponse( - parseModeFromJson(root, defaultMode), parseNetworkFeeFromJson(root), parseFeeEstimateFromJson(root, "node"), parseHighVolumeMultiplierFromJson(root), @@ -167,23 +158,14 @@ private static long parseTotalFromJson(JsonObject root) { return root.has("total") ? root.get("total").getAsLong() : 0L; } - /** - * Extract the fee estimate mode used. - * - * @return the fee estimate mode - */ - public FeeEstimateMode getMode() { - return mode; - } - /** * Extract the network fee component. * * @return the network fee component, or null if not set */ @Nullable - public NetworkFee getNetworkFee() { - return networkFee; + public NetworkFee getNetwork() { + return network; } /** @@ -192,8 +174,8 @@ public NetworkFee getNetworkFee() { * @return the node fee estimate, or null if not set */ @Nullable - public FeeEstimate getNodeFee() { - return nodeFee; + public FeeEstimate getNode() { + return node; } /** @@ -211,8 +193,8 @@ public long getHighVolumeMultiplier() { * @return the service fee estimate, or null if not set */ @Nullable - public FeeEstimate getServiceFee() { - return serviceFee; + public FeeEstimate getService() { + return service; } /** @@ -227,11 +209,10 @@ public long getTotal() { @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("mode", mode) - .add("network", networkFee) - .add("node", nodeFee) + .add("network", network) + .add("node", node) .add("highVolumeMultiplier", highVolumeMultiplier) - .add("service", serviceFee) + .add("service", service) .add("total", total) .toString(); } @@ -245,15 +226,14 @@ public boolean equals(Object o) { return false; } return total == that.total - && mode == that.mode && highVolumeMultiplier == that.highVolumeMultiplier - && Objects.equals(networkFee, that.networkFee) - && Objects.equals(nodeFee, that.nodeFee) - && Objects.equals(serviceFee, that.serviceFee); + && Objects.equals(network, that.network) + && Objects.equals(node, that.node) + && Objects.equals(service, that.service); } @Override public int hashCode() { - return Objects.hash(mode, networkFee, nodeFee, highVolumeMultiplier, serviceFee, total); + return Objects.hash(network, node, highVolumeMultiplier, service, total); } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/FeeEstimateQueryMockTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/FeeEstimateQueryMockTest.java index 1655ad8c0c..e522fb9900 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/FeeEstimateQueryMockTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/FeeEstimateQueryMockTest.java @@ -62,7 +62,6 @@ void retriesOnUnavailableErrors() throws IOException, InterruptedException { var response = query.execute(client); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.STATE); assertThat(response.getTotal()).isEqualTo(26); assertThat(stub.requestCount()).isEqualTo(2); } @@ -78,7 +77,6 @@ void retriesOnDeadlineExceededErrors() throws IOException, InterruptedException var response = query.execute(client); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.STATE); assertThat(response.getTotal()).isEqualTo(60); assertThat(stub.requestCount()).isEqualTo(2); } @@ -92,7 +90,6 @@ void succeedsOnFirstAttempt() throws IOException, InterruptedException { var response = query.execute(client); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.INTRINSIC); assertThat(response.getTotal()).isEqualTo(3 * 10 + 10 + 20); assertThat(response.getHighVolumeMultiplier()).isEqualTo(1); assertThat(stub.requestCount()).isEqualTo(1); @@ -109,7 +106,6 @@ void defaultsToIntrinsic() throws IOException, InterruptedException { var response = query.execute(client); assertThat(stub.getLastQueryParams()).contains("mode=INTRINSIC"); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.INTRINSIC); } @Test diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java index 17f05427b5..f59b15fb5c 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java @@ -168,7 +168,7 @@ void tokenMintTransactionFeeEstimate() throws Throwable { .execute(testEnv.client); assertFeeComponentsPresent(response); - assertThat(response.getNodeFee().getExtras()).isNotNull(); + assertThat(response.getNode().getExtras()).isNotNull(); assertComponentTotalsConsistent(response); } } @@ -393,19 +393,19 @@ private static void assertFeeComponentsPresent(FeeEstimateResponse response) { assertThat(response).isNotNull(); // Network fee validations - assertThat(response.getNetworkFee()).isNotNull(); - assertThat(response.getNetworkFee().getMultiplier()).isGreaterThan(0); - assertThat(response.getNetworkFee().getSubtotal()).isGreaterThanOrEqualTo(0); + assertThat(response.getNetwork()).isNotNull(); + assertThat(response.getNetwork().getMultiplier()).isGreaterThan(0); + assertThat(response.getNetwork().getSubtotal()).isGreaterThanOrEqualTo(0); // Node fee validations - assertThat(response.getNodeFee()).isNotNull(); - assertThat(response.getNodeFee().getBase()).isGreaterThanOrEqualTo(0); - assertThat(response.getNodeFee().getExtras()).isNotNull(); + assertThat(response.getNode()).isNotNull(); + assertThat(response.getNode().getBase()).isGreaterThanOrEqualTo(0); + assertThat(response.getNode().getExtras()).isNotNull(); // Service fee validations - assertThat(response.getServiceFee()).isNotNull(); - assertThat(response.getServiceFee().getBase()).isGreaterThanOrEqualTo(0); - assertThat(response.getServiceFee().getExtras()).isNotNull(); + assertThat(response.getService()).isNotNull(); + assertThat(response.getService().getBase()).isGreaterThanOrEqualTo(0); + assertThat(response.getService().getExtras()).isNotNull(); // High volume multiplier and total assertThat(response.getHighVolumeMultiplier()).isGreaterThanOrEqualTo(1); @@ -414,9 +414,9 @@ private static void assertFeeComponentsPresent(FeeEstimateResponse response) { private static void assertComponentTotalsConsistent(FeeEstimateResponse response) { // TODO adjust when NetworkService.getFeeEstimate has actual implementation - var network = response.getNetworkFee(); - var node = response.getNodeFee(); - var service = response.getServiceFee(); + var network = response.getNetwork(); + var node = response.getNode(); + var service = response.getService(); var nodeSubtotal = subtotal(node); var serviceSubtotal = subtotal(service); From c1a45caef9e8cdb956a0dcf25587518dd05d16a3 Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Wed, 29 Apr 2026 14:02:57 +0300 Subject: [PATCH 5/6] refactor: variable names Signed-off-by: Mustafa Uzun --- .../sdk/test/integration/FeeEstimateQueryIntegrationTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java index f59b15fb5c..8ae23314aa 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/FeeEstimateQueryIntegrationTest.java @@ -54,7 +54,6 @@ void tokenCreateTransactionFeeEstimate() throws Throwable { // Then: The response includes appropriate fees assertFeeComponentsPresent(response); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.STATE); assertComponentTotalsConsistent(response); } } @@ -78,7 +77,6 @@ void transferTransactionStateModeFeeEstimate() throws Throwable { .execute(testEnv.client); assertFeeComponentsPresent(response); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.STATE); assertComponentTotalsConsistent(response); } } @@ -101,7 +99,6 @@ void transferTransactionIntrinsicModeFeeEstimate() throws Throwable { .execute(testEnv.client); assertFeeComponentsPresent(response); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.INTRINSIC); assertComponentTotalsConsistent(response); } } @@ -122,7 +119,6 @@ void transferTransactionDefaultModeIsIntrinsic() throws Throwable { var response = new FeeEstimateQuery().setTransaction(transaction).execute(testEnv.client); assertFeeComponentsPresent(response); - assertThat(response.getMode()).isEqualTo(FeeEstimateMode.INTRINSIC); assertComponentTotalsConsistent(response); } } From fe6857717fc437ea2b9f591b8b494447b4744858 Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Wed, 29 Apr 2026 14:16:39 +0300 Subject: [PATCH 6/6] style: formatting Signed-off-by: Mustafa Uzun --- .../hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java | 3 +-- .../main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java index 3b86a5e35f..4d38220c6a 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/FeeEstimateQueryExample.java @@ -159,8 +159,7 @@ private static FeeEstimateResponse estimateWithIntrinsicMode(Client client, Tran System.out.println( "Network Fee Subtotal: " + intrinsicEstimate.getNetwork().getSubtotal() + " tinycents"); System.out.println("Node Fee Base: " + intrinsicEstimate.getNode().getBase() + " tinycents"); - System.out.println( - "Service Fee Base: " + intrinsicEstimate.getService().getBase() + " tinycents"); + System.out.println("Service Fee Base: " + intrinsicEstimate.getService().getBase() + " tinycents"); System.out.println("Total Estimated Fee: " + intrinsicEstimate.getTotal() + " tinycents"); System.out.println("Total Estimated Fee: " + Hbar.fromTinybars(intrinsicEstimate.getTotal() / 100)); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java index 434f794662..37371113b6 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/FeeEstimateQuery.java @@ -230,8 +230,7 @@ public FeeEstimateResponse execute(Client client, Duration timeout) throws IOExc /** * Handle the HTTP response and return the result or null if retry is needed. */ - private FeeEstimateResponse handleResponse( - HttpResponse response, int attempt) { + private FeeEstimateResponse handleResponse(HttpResponse response, int attempt) { if (isSuccessfulResponse(response.statusCode())) { return FeeEstimateResponse.fromJson(response.body()); }