Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 ===");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/
public enum FeeEstimateMode {
/**
* Default mode: uses latest known state.
* Uses latest known state.
* <p>
* This mode calculates fees based on the current state of the network,
* taking into account all state-dependent factors such as current
Expand All @@ -17,7 +17,7 @@ public enum FeeEstimateMode {
STATE(0),

/**
* Intrinsic mode: ignores state-dependent factors.
* Default mode: ignores state-dependent factors.
* <p>
* This mode calculates fees based only on the intrinsic properties of
* the transaction itself, ignoring dynamic network conditions. This
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -64,7 +65,7 @@ public FeeEstimateMode getMode() {
/**
* Set the mode for fee estimation.
* <p>
* 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}
Expand All @@ -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%).
* <p>
* 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.
*
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -253,7 +280,7 @@ public CompletableFuture<FeeEstimateResponse> executeAsync(Client client) {
* @return the fee estimate response
*/
public CompletableFuture<FeeEstimateResponse> executeAsync(Client client, Duration timeout) {
var resolvedMode = mode != null ? mode : FeeEstimateMode.STATE;
var resolvedMode = mode != null ? mode : FeeEstimateMode.INTRINSIC;
CompletableFuture<FeeEstimateResponse> returnFuture = new CompletableFuture<>();
executeAsync(client, timeout, resolvedMode, returnFuture, 1);
return returnFuture;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
* <p>
* 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<String> notes;
private final long highVolumeMultiplier;

/**
* The sum of the network, node, and service subtotals in tinycents.
Expand All @@ -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<String> 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;
}
Expand All @@ -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));
}
Expand Down Expand Up @@ -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<String> parseNotesFromJson(JsonObject root) {
List<String> 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;
}

/**
Expand Down Expand Up @@ -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<String> getNotes() {
return notes;
public long getHighVolumeMultiplier() {
return highVolumeMultiplier;
}

/**
Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
}
24 changes: 10 additions & 14 deletions sdk/src/main/java/com/hedera/hashgraph/sdk/FeeExtra.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}

Expand All @@ -97,7 +97,7 @@ public int getCharged() {
*
* @return the actual count of items
*/
public int getCount() {
public long getCount() {
return count;
}

Expand All @@ -115,7 +115,7 @@ public long getFeePerUnit() {
*
* @return the count included for free
*/
public int getIncluded() {
public long getIncluded() {
return included;
}

Expand Down Expand Up @@ -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();
}
Expand Down
Loading
Loading