Skip to content

feat: add global set_default_max_transaction_fee to Client#2026

Open
AntonioCeppellini wants to merge 2 commits intohiero-ledger:mainfrom
AntonioCeppellini:2000-add-set_max_transaction_fee-to-Client
Open

feat: add global set_default_max_transaction_fee to Client#2026
AntonioCeppellini wants to merge 2 commits intohiero-ledger:mainfrom
AntonioCeppellini:2000-add-set_max_transaction_fee-to-Client

Conversation

@AntonioCeppellini
Copy link
Copy Markdown
Member

Description:
This PR introduces a global default_max_transaction_fee at the Client level.

Add support for ...

  • new field default_max_transaction_fee in client.py default value as None
  • new setter method set_default_max_transaction_fee
  • Updates _default_transaction_fee to be 2Hbar in the transaction.py
  • Updates freeze_with() in transaction.py to resolve max_transaction_fee value
  • New setter method set_transaction_fee() in transaction.py to set the value of self.transaction_fee
  • Tests

Related issue(s):

Fixes #2000

Checklist

  • Documented (Code comments, README, etc.)
  • Tested (unit, integration, etc.)

Signed-off-by: Antonio Ceppellini <antonio.ceppellini@gmail.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 95.08197% with 3 lines in your changes missing coverage. Please review.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2026      +/-   ##
==========================================
+ Coverage   93.66%   93.70%   +0.03%     
==========================================
  Files         144      144              
  Lines        9350     9376      +26     
==========================================
+ Hits         8758     8786      +28     
+ Misses        592      590       -2     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

Walkthrough

Adds a client-level default max transaction fee and per-transaction fee API: introduces Client.default_max_transaction_fee and Client.set_default_max_transaction_fee(), adds Transaction.set_transaction_fee(), changes transaction default fee to Hbar(2), and makes freeze_with() resolve fees from client default or transaction default. Tests and changelog updated.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Inserted Unreleased → Added entry documenting the new set_default_max_transaction_fee() client API.
Client Configuration
src/hiero_sdk_python/client/client.py
Added `default_max_transaction_fee: Hbar
Transaction Fee Management
src/hiero_sdk_python/transaction/transaction.py
Added TransactionFee = Union[int, Hbar] alias and set_transaction_fee() method with type and non-negative checks; changed transaction_fee typing to `TransactionFee
Client Unit Tests
tests/unit/client_test.py
Added tests for set_default_max_transaction_fee() verifying it sets/overwrites the client field, returns self, enforces Hbar type, and rejects negative Hbar. Removed an unused import.
Transaction Unit Tests
tests/unit/transaction_freeze_and_bytes_test.py
Added _DummyTransaction helper and tests for set_transaction_fee() (accepts int/Hbar, rejects invalid types/negatives) and freeze_with() fee-resolution behavior (explicit fee preserved; otherwise use client default; otherwise use transaction default Hbar(2)).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Client
    participant Transaction
    participant FreezeLogic

    User->>Client: set_default_max_transaction_fee(Hbar(2))
    Client->>Client: validate(Hbar) / store default_max_transaction_fee

    User->>Transaction: new Transaction(...)  (transaction_fee = None)
    User->>Transaction: freeze_with(client)
    activate FreezeLogic
    FreezeLogic->>Transaction: is transaction_fee None?
    alt transaction_fee is None
        FreezeLogic->>Client: read default_max_transaction_fee
        alt client.default_max_transaction_fee != None
            Client-->>FreezeLogic: Hbar(2)
            FreezeLogic->>Transaction: set transaction_fee = client default
        else
            FreezeLogic->>Transaction: set transaction_fee = _default_transaction_fee (Hbar(2))
        end
    else
        FreezeLogic->>Transaction: keep existing transaction_fee
    end
    deactivate FreezeLogic
    Transaction-->>User: frozen transaction (transaction_fee resolved)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a global set_default_max_transaction_fee method to the Client class, which is the primary objective of this PR.
Description check ✅ Passed The description is directly related to the changeset, detailing the introduction of a global default_max_transaction_fee at the Client level and related transaction-level handling with appropriate references to issue #2000.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #2000: adds default_max_transaction_fee field to Client, implements set_default_max_transaction_fee setter, updates _default_transaction_fee to 2 Hbar, adds set_transaction_fee method, implements fee resolution in freeze_with(), and includes comprehensive tests.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objectives: Client fee field/setter, transaction fee setter, fee resolution logic, default value updates, and corresponding unit tests. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #2000

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3eaa559-7c21-4dd5-b3de-06cc6e06502c

📥 Commits

Reviewing files that changed from the base of the PR and between d2b19dc and 3a431e4.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/hiero_sdk_python/client/client.py
  • src/hiero_sdk_python/transaction/transaction.py
  • tests/unit/client_test.py
  • tests/unit/transaction_freeze_and_bytes_test.py

@github-actions
Copy link
Copy Markdown

Hi, this is WorkflowBot.
Your pull request cannot be merged as it is not passing all our workflow checks.
Please click on each check to review the logs and resolve issues so all checks pass.
To help you:

@exploreriii exploreriii requested a review from Akshat8510 March 26, 2026 11:44
@exploreriii exploreriii added status: needs committer review PR needs a review from the committer team status: needs maintainer review PR needs a review from the maintainer team labels Mar 26, 2026
self.transaction_id = transaction_id
return self

def set_transaction_fee(self, transaction_fee) -> "Transaction":
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be nice if we also accept the Hbar and int both as input

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! :D

@manishdait
Copy link
Copy Markdown
Contributor

Hey @AntonioCeppellini, great work on this!,
Just a couple of suggestions, the e2e tests are currently failing due to _apply_tiny_max_fee_if_supported() it looks like it’s setting the max transaction fee in hbar.

I think this should be resolved once the requested changes are applied

@AntonioCeppellini
Copy link
Copy Markdown
Member Author

@manishdait thank you! new commit incoming :D

Signed-off-by: Antonio Ceppellini <antonio.ceppellini@gmail.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
tests/unit/client_test.py (1)

497-502: 🧹 Nitpick | 🔵 Trivial

Add boundary test for Hbar(0) and remove unnecessary f-string prefix.

Per unit test guidelines (PRIORITY 3), boundary conditions should be tested. Hbar(0) is a valid edge case that should be accepted. Also, the f-string prefix is unnecessary since there's no interpolation.

💚 Proposed additions
 def test_set_invalid_value_default_max_transaction_fee():
     """Test that set invalid value with set_max_transaction fee."""
     client = Client.for_testnet()

-    with pytest.raises(ValueError, match=f"default_max_transaction_fee must be >= 0"):
+    with pytest.raises(ValueError, match="default_max_transaction_fee must be >= 0"):
         client.set_default_max_transaction_fee(Hbar(-1))
+
+
+def test_set_default_max_transaction_fee_zero_is_valid():
+    """Test that set_default_max_transaction_fee accepts zero as a valid boundary value."""
+    client = Client.for_testnet()
+
+    returned = client.set_default_max_transaction_fee(Hbar(0))
+
+    assert returned is client
+    assert client.default_max_transaction_fee == Hbar(0)
+
+    client.close()

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c5f6e36f-e76a-4326-bca6-646bb642a1a4

📥 Commits

Reviewing files that changed from the base of the PR and between 3a431e4 and a291b90.

📒 Files selected for processing (4)
  • src/hiero_sdk_python/client/client.py
  • src/hiero_sdk_python/transaction/transaction.py
  • tests/unit/client_test.py
  • tests/unit/transaction_freeze_and_bytes_test.py

Comment on lines +317 to +321
if self.transaction_fee is None:
if client.default_max_transaction_fee is not None:
self.transaction_fee = client.default_max_transaction_fee
else:
self.transaction_fee = self._default_transaction_fee
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify FileAppendTransaction.freeze_with() does not call super()
rg -n "def freeze_with" src/hiero_sdk_python/file/file_append_transaction.py -A 50 | head -60

# Check if super().freeze_with is called
rg -n "super\(\)\.freeze_with" src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 2531


🏁 Script executed:

# Read transaction.py to see the full freeze_with implementation around lines 317-321
sed -n '300,350p' src/hiero_sdk_python/transaction/transaction.py

# Also check FileAppendTransaction's __init__ or any fee-related logic
rg -n "transaction_fee\|_default_transaction_fee" src/hiero_sdk_python/file/file_append_transaction.py -B 2 -A 2

Repository: hiero-ledger/hiero-sdk-python

Length of output: 2052


🏁 Script executed:

# Check FileAppendTransaction class definition and inheritance
rg -n "^class FileAppendTransaction" src/hiero_sdk_python/file/file_append_transaction.py -A 3

# Check __init__ method for fee initialization
rg -n "def __init__" src/hiero_sdk_python/file/file_append_transaction.py -A 30 | head -50

# Check if FileAppendTransaction or parent Transaction sets any default fee
rg -n "transaction_fee" src/hiero_sdk_python/transaction/transaction.py | head -20

Repository: hiero-ledger/hiero-sdk-python

Length of output: 3278


🏁 Script executed:

# Check FileAppendTransaction.build_transaction_body() implementation
rg -n "def build_transaction_body" src/hiero_sdk_python/file/file_append_transaction.py -A 30

# Check if FileAppendTransaction has any fee-related logic
rg -n "transactionFee\|transaction_fee" src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1399


🏁 Script executed:

# Check Transaction.build_base_transaction_body() to see how it handles transaction_fee
rg -n "def build_base_transaction_body" src/hiero_sdk_python/transaction/transaction.py -A 20

Repository: hiero-ledger/hiero-sdk-python

Length of output: 973


🏁 Script executed:

# Get more lines from build_base_transaction_body to see fee handling
sed -n '491,550p' src/hiero_sdk_python/transaction/transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 2430


🏁 Script executed:

# Check for FileAppendTransaction tests, especially fee-related
find . -name "*test*file*append*" -o -name "*file_append*test*" 2>/dev/null | head -10

# Also search for any test files that test FileAppendTransaction
rg -l "FileAppendTransaction" --type py | grep -i test

Repository: hiero-ledger/hiero-sdk-python

Length of output: 270


🏁 Script executed:

# Check unit tests for FileAppendTransaction, especially fee-related tests
rg -n "transaction_fee\|default.*fee\|freeze_with" tests/unit/file_append_transaction_test.py | head -30

# Get overall structure of the test file
wc -l tests/unit/file_append_transaction_test.py
head -100 tests/unit/file_append_transaction_test.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 3784


🏁 Script executed:

# Search for more fee-related tests in the file
rg -n "default.*fee\|transaction_fee" tests/unit/file_append_transaction_test.py -B 3 -A 10

# Also check integration tests
rg -n "default.*fee\|transaction_fee" tests/integration/file_append_transaction_e2e_test.py -B 3 -A 10

Repository: hiero-ledger/hiero-sdk-python

Length of output: 55


🏁 Script executed:

# Check for any comments or documentation in FileAppendTransaction about why freeze_with is overridden
sed -n '270,330p' src/hiero_sdk_python/file/file_append_transaction.py | head -70

# Check if there are any other Transaction subclasses that override freeze_with
rg -n "def freeze_with" src/hiero_sdk_python --type py | grep -v "file_append_transaction"

Repository: hiero-ledger/hiero-sdk-python

Length of output: 2911


🏁 Script executed:

# Check if TokenCreateTransaction.freeze_with calls super()
rg -n "def freeze_with" src/hiero_sdk_python/tokens/token_create_transaction.py -A 30 | head -40

# Check TopicMessageSubmitTransaction.freeze_with
rg -n "def freeze_with" src/hiero_sdk_python/consensus/topic_message_submit_transaction.py -A 30 | head -40

Repository: hiero-ledger/hiero-sdk-python

Length of output: 2770


🏁 Script executed:

# Let me verify the exact difference in structure between TopicMessageSubmitTransaction and FileAppendTransaction
# to understand why one calls super() and the other doesn't

# Check the full context of how TopicMessageSubmitTransaction handles the chunking + super call
sed -n '258,287p' src/hiero_sdk_python/consensus/topic_message_submit_transaction.py

# Check if there are any comments in FileAppendTransaction explaining the override design
sed -n '265,275p' src/hiero_sdk_python/file/file_append_transaction.py

Repository: hiero-ledger/hiero-sdk-python

Length of output: 1752


FileAppendTransaction.freeze_with() bypasses fee auto-population.

The fee auto-population logic at lines 317-321 (client default fallback) is skipped for FileAppendTransaction because its freeze_with() override manually builds all transaction bodies without calling super().freeze_with(client). This causes all chunks to use the hardcoded _default_transaction_fee (Hbar 5) and ignore client.default_max_transaction_fee.

In contrast, TopicMessageSubmitTransaction (another chunked transaction) correctly performs chunk setup then delegates to super().freeze_with(client) to apply the fee logic. FileAppendTransaction should follow this pattern.

Comment on lines +682 to +688
if isinstance(transaction_fee, int):
if transaction_fee < 0:
raise ValueError("transaction_fee must be >= 0")

if isinstance(transaction_fee, Hbar):
if transaction_fee < Hbar(0):
raise ValueError("transaction_fee must be >= 0")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Simplify negative value validation by consolidating checks.

The separate if isinstance(..., int) and if isinstance(..., Hbar) blocks for negative value checking can be simplified. Additionally, bool is a subclass of int in Python, so True/False would pass the int check and potentially be stored.

♻️ Proposed simplification
+        if isinstance(transaction_fee, bool):
+            raise TypeError(
+                f"transaction_fee must be an int or Hbar, got {type(transaction_fee).__name__}"
+            )
+
         if isinstance(transaction_fee, int):
             if transaction_fee < 0:
                 raise ValueError("transaction_fee must be >= 0")
-
-        if isinstance(transaction_fee, Hbar):
-            if transaction_fee < Hbar(0):
+        elif isinstance(transaction_fee, Hbar):
+            if transaction_fee.to_tinybars() < 0:
                 raise ValueError("transaction_fee must be >= 0")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if isinstance(transaction_fee, int):
if transaction_fee < 0:
raise ValueError("transaction_fee must be >= 0")
if isinstance(transaction_fee, Hbar):
if transaction_fee < Hbar(0):
raise ValueError("transaction_fee must be >= 0")
if isinstance(transaction_fee, bool):
raise TypeError(
f"transaction_fee must be an int or Hbar, got {type(transaction_fee).__name__}"
)
if isinstance(transaction_fee, int):
if transaction_fee < 0:
raise ValueError("transaction_fee must be >= 0")
elif isinstance(transaction_fee, Hbar):
if transaction_fee.to_tinybars() < 0:
raise ValueError("transaction_fee must be >= 0")

Comment on lines +637 to +645
@pytest.mark.parametrize("value", [0, 1, 100_000_000, Hbar(1)])
def test_set_transaction_fee(value):
"""set_transaction_fee() stores valid integer fees."""
transaction = TransferTransaction()

returned = transaction.set_transaction_fee(value)

assert returned is transaction
assert transaction.transaction_fee == value
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor docstring inaccuracy: test accepts both int and Hbar.

The docstring says "stores valid integer fees" but the test parametrization includes Hbar(1). Consider updating the docstring for accuracy.

📝 Suggested docstring fix
 `@pytest.mark.parametrize`("value", [0, 1, 100_000_000, Hbar(1)])
 def test_set_transaction_fee(value):
-    """set_transaction_fee() stores valid integer fees."""
+    """set_transaction_fee() stores valid int or Hbar fees."""
     transaction = TransferTransaction()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@pytest.mark.parametrize("value", [0, 1, 100_000_000, Hbar(1)])
def test_set_transaction_fee(value):
"""set_transaction_fee() stores valid integer fees."""
transaction = TransferTransaction()
returned = transaction.set_transaction_fee(value)
assert returned is transaction
assert transaction.transaction_fee == value
`@pytest.mark.parametrize`("value", [0, 1, 100_000_000, Hbar(1)])
def test_set_transaction_fee(value):
"""set_transaction_fee() stores valid int or Hbar fees."""
transaction = TransferTransaction()
returned = transaction.set_transaction_fee(value)
assert returned is transaction
assert transaction.transaction_fee == value

@manishdait
Copy link
Copy Markdown
Contributor

Hi @AntonioCeppellini,

In test_account_update_insufficient_fee_with_valid_expiration_bump(), the e2e test for AccountUpdateTransaction is currently asserting that the receipt status is INSUFFICIENT_TX_FEE. However, this scenario should ideally raise a PrecheckError instead.

Proposed change:

receipt = tx.execute(env.client)
assert receipt.status == ResponseCode.INSUFFICIENT_TX_FEE, (
    f"Expected INSUFFICIENT_TX_FEE but got {ResponseCode(receipt.status).name}"
)

# Replace this with

with pytest.raises(PrecheckError) as e:
    tx.execute(env.client)

assert e.value.status == ResponseCode.INSUFFICIENT_TX_FEE

Right now, the test is relying on _apply_tiny_max_fee_if_supported, which previously skip this behavior. After introducing set_transaction_fee this workaround is no longer appropriate.

@manishdait
Copy link
Copy Markdown
Contributor

Also We can simplify this by removing _apply_tiny_max_fee_if_supported by using set_transaction_fee() directly

@manishdait manishdait added status: needs developer revision PR has requested changes that the developer needs to implement and removed status: needs committer review PR needs a review from the committer team status: needs maintainer review PR needs a review from the maintainer team labels Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: needs developer revision PR has requested changes that the developer needs to implement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add global set_max_transaction_fee() to Client

3 participants