Skip to content

Commit 269a429

Browse files
feat: set_max_transaction to client
Signed-off-by: tech0priyanshu <priyanshuyadv101106@gmail.com>
1 parent f2015e9 commit 269a429

4 files changed

Lines changed: 182 additions & 6 deletions

File tree

src/hiero_sdk_python/client/client.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__(self, network: Network = None) -> None:
5959

6060
self.max_attempts: int = 10
6161
self.default_max_query_payment: Hbar = DEFAULT_MAX_QUERY_PAYMENT
62+
self.default_max_transaction_fee: Hbar = None
6263

6364
self._min_backoff: float = DEFAULT_MIN_BACKOFF
6465
self._max_backoff: float = DEFAULT_MAX_BACKOFF
@@ -288,6 +289,27 @@ def set_default_max_query_payment(self, max_query_payment: int | float | Decimal
288289
self.default_max_query_payment = value
289290
return self
290291

292+
def set_max_transaction_fee(
293+
self,
294+
max_transaction_fee: int | float | Decimal | Hbar,
295+
) -> Client:
296+
297+
if isinstance(max_transaction_fee, bool) or not isinstance(
298+
max_transaction_fee,
299+
(int, float, Decimal, Hbar),
300+
):
301+
raise TypeError(
302+
f"max_transaction_fee must be int, float, Decimal, or Hbar, got {type(max_transaction_fee).__name__}"
303+
)
304+
305+
value = max_transaction_fee if isinstance(max_transaction_fee, Hbar) else Hbar(max_transaction_fee)
306+
307+
if value < Hbar(0):
308+
raise ValueError("max_transaction_fee must be non-negative")
309+
310+
self.default_max_transaction_fee = value
311+
return self
312+
291313
def set_max_attempts(self, max_attempts: int) -> Client:
292314
"""
293315
Set the maximum number of execution attempts for all transactions and queries

src/hiero_sdk_python/transaction/transaction.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
from __future__ import annotations
22

33
import hashlib
4+
from decimal import Decimal
45
from typing import TYPE_CHECKING, Literal, overload
56

67
from hiero_sdk_python.account.account_id import AccountId
78
from hiero_sdk_python.client.client import Client
89
from hiero_sdk_python.crypto.key import Key
910
from hiero_sdk_python.exceptions import PrecheckError
1011
from hiero_sdk_python.executable import _Executable, _ExecutionState
11-
from hiero_sdk_python.hapi.services import basic_types_pb2, transaction_contents_pb2, transaction_pb2
12-
from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import SchedulableTransactionBody
13-
from hiero_sdk_python.hapi.services.transaction_response_pb2 import TransactionResponse as TransactionResponseProto
12+
from hiero_sdk_python.hapi.services import (
13+
basic_types_pb2,
14+
transaction_contents_pb2,
15+
transaction_pb2,
16+
)
17+
from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import (
18+
SchedulableTransactionBody,
19+
)
20+
from hiero_sdk_python.hapi.services.transaction_response_pb2 import (
21+
TransactionResponse as TransactionResponseProto,
22+
)
1423
from hiero_sdk_python.hbar import Hbar
1524
from hiero_sdk_python.query.fee_estimate_query import FeeEstimateQuery
1625
from hiero_sdk_python.response_code import ResponseCode
@@ -314,6 +323,16 @@ def freeze_with(self, client: Client):
314323

315324
else:
316325
# Use all nodes from client network
326+
# Resolve fee priority before building bodies:
327+
# 1. Explicit transaction fee (self.transaction_fee)
328+
# 2. Client default_max_transaction_fee
329+
# 3. Transaction class default (_default_transaction_fee)
330+
if self.transaction_fee is None:
331+
if client is not None and getattr(client, "default_max_transaction_fee", None) is not None:
332+
self.transaction_fee = client.default_max_transaction_fee
333+
else:
334+
self.transaction_fee = self._default_transaction_fee
335+
317336
for node in client.network.nodes:
318337
self.node_account_id = node._account_id
319338
self._transaction_body_bytes[node._account_id] = self.build_transaction_body().SerializeToString()
@@ -746,6 +765,22 @@ def from_bytes(transaction_bytes: bytes):
746765
transaction_body, signed_transaction.bodyBytes, signed_transaction.sigMap
747766
)
748767

768+
def set_max_transaction_fee(self, max_transaction_fee):
769+
# Accept int, float, Decimal, or Hbar (but not bool)
770+
771+
if isinstance(max_transaction_fee, bool) or not isinstance(max_transaction_fee, (int, float, Decimal, Hbar)):
772+
raise TypeError(
773+
f"max_transaction_fee must be int, float, Decimal, or Hbar, got {type(max_transaction_fee).__name__}"
774+
)
775+
776+
value = max_transaction_fee if isinstance(max_transaction_fee, Hbar) else Hbar(max_transaction_fee)
777+
778+
if value < Hbar(0):
779+
raise ValueError("max_transaction_fee must be non-negative")
780+
781+
self.transaction_fee = value
782+
return self
783+
749784
@staticmethod
750785
def _get_transaction_class(transaction_type: str):
751786
"""

tests/unit/client_test.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,49 @@ def test_set_default_max_query_payment_valid_param(valid_amount, expected):
231231
assert client.default_max_query_payment == expected
232232

233233

234+
def test_default_max_transaction_fee_is_none():
235+
"""Default `default_max_transaction_fee` should be None."""
236+
client = Client.for_testnet()
237+
assert client.default_max_transaction_fee is None
238+
239+
240+
@pytest.mark.parametrize(
241+
"valid_amount,expected",
242+
[
243+
(1, Hbar(1)),
244+
(0.1, Hbar(0.1)),
245+
(Decimal("0.1"), Hbar(Decimal("0.1"))),
246+
(Hbar(1), Hbar(1)),
247+
(Hbar(0), Hbar(0)),
248+
],
249+
)
250+
def test_set_default_max_transaction_fee_valid_param(valid_amount, expected):
251+
"""Test set_max_transaction_fee converts inputs to Hbar and stores them."""
252+
client = Client.for_testnet()
253+
254+
returned = client.set_max_transaction_fee(valid_amount)
255+
assert client.default_max_transaction_fee == expected
256+
assert returned is client
257+
258+
259+
@pytest.mark.parametrize("invalid_amount", ["1", True, False, None, object()])
260+
def test_set_default_max_transaction_fee_invalid_param(invalid_amount):
261+
"""Test set_max_transaction_fee rejects invalid types."""
262+
client = Client.for_testnet()
263+
264+
with pytest.raises(TypeError):
265+
client.set_max_transaction_fee(invalid_amount)
266+
267+
268+
@pytest.mark.parametrize("negative_amount", [-1, -0.1, Decimal("-0.1"), Hbar(-1)])
269+
def test_set_default_max_transaction_fee_negative_value(negative_amount):
270+
"""Test set_max_transaction_fee rejects negative values."""
271+
client = Client.for_testnet()
272+
273+
with pytest.raises(ValueError):
274+
client.set_max_transaction_fee(negative_amount)
275+
276+
234277
@pytest.mark.parametrize("negative_amount", [-1, -0.1, Decimal("-0.1"), Decimal("-1"), Hbar(-1)])
235278
def test_set_default_max_query_payment_negative_value(negative_amount):
236279
"""Test set_default_max_query_payment for negative amount values."""

tests/unit/transaction_freeze_and_bytes_test.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88

99
from __future__ import annotations
1010

11+
from decimal import Decimal
12+
1113
import pytest
1214

1315
from hiero_sdk_python.account.account_id import AccountId
1416
from hiero_sdk_python.crypto.private_key import PrivateKey
1517
from hiero_sdk_python.hapi.services.transaction_response_pb2 import (
1618
TransactionResponse as TransactionResponseProto,
1719
)
20+
from hiero_sdk_python.hbar import Hbar
1821
from hiero_sdk_python.transaction.transaction_id import TransactionId
1922
from hiero_sdk_python.transaction.transfer_transaction import TransferTransaction
2023

@@ -59,9 +62,44 @@ def test_freeze_with_valid_parameters():
5962
# Should return self for method chaining
6063
assert result is transaction
6164

62-
# Should have transaction body bytes set
63-
assert len(transaction._transaction_body_bytes) > 0
64-
assert node_id in transaction._transaction_body_bytes
65+
66+
@pytest.mark.parametrize(
67+
"valid_amount,expected",
68+
[
69+
(1, Hbar(1)),
70+
(0.1, Hbar(0.1)),
71+
(Decimal("0.1"), Hbar(Decimal("0.1"))),
72+
(Hbar(1), Hbar(1)),
73+
(Hbar(0), Hbar(0)),
74+
],
75+
)
76+
def test_set_max_transaction_fee_valid_param(valid_amount, expected):
77+
"""Transaction.set_max_transaction_fee should accept various numeric types and Hbar."""
78+
tx = TransferTransaction()
79+
80+
returned = tx.set_max_transaction_fee(valid_amount)
81+
assert tx.transaction_fee == expected
82+
assert returned is tx
83+
84+
85+
@pytest.mark.parametrize("invalid_amount", ["1", True, False, None, object()])
86+
def test_set_max_transaction_fee_invalid_param(invalid_amount):
87+
"""Transaction.set_max_transaction_fee should reject invalid types."""
88+
tx = TransferTransaction()
89+
90+
with pytest.raises(TypeError):
91+
tx.set_max_transaction_fee(invalid_amount)
92+
93+
94+
@pytest.mark.parametrize("negative_amount", [-1, -0.1, Decimal("-0.1"), Hbar(-1)])
95+
def test_set_max_transaction_fee_negative_value(negative_amount):
96+
"""Transaction.set_max_transaction_fee should reject negative values."""
97+
tx = TransferTransaction()
98+
99+
with pytest.raises(ValueError):
100+
tx.set_max_transaction_fee(negative_amount)
101+
# checking state un modified
102+
assert len(tx._transaction_body_bytes) == 0
65103

66104

67105
def test_freeze_is_idempotent():
@@ -685,3 +723,41 @@ def test_map_response_raises_if_proto_request_is_not_transaction():
685723
node_id=mock_node_id,
686724
proto_request=invalid_proto_request,
687725
)
726+
727+
728+
def test_fee_resolution_transaction_precedence(mock_client):
729+
"""Transaction fee explicitly set should take precedence over client default."""
730+
tx = TransferTransaction()
731+
tx.set_max_transaction_fee(Hbar(10))
732+
733+
# client has different default
734+
mock_client.set_max_transaction_fee(Hbar(5))
735+
736+
tx.freeze_with(mock_client)
737+
738+
assert tx.transaction_fee == Hbar(10)
739+
740+
741+
def test_fee_resolution_client_default_used_when_transaction_missing(mock_client):
742+
"""When transaction fee is not set, client.default_max_transaction_fee should be used."""
743+
tx = TransferTransaction()
744+
# leave tx.transaction_fee as None
745+
746+
mock_client.set_max_transaction_fee(Hbar(7))
747+
748+
tx.freeze_with(mock_client)
749+
750+
assert tx.transaction_fee == Hbar(7)
751+
752+
753+
def test_fee_resolution_falls_back_to_transaction_default(mock_client):
754+
"""When neither transaction nor client provide a fee, fallback to transaction default Hbar(1)."""
755+
tx = TransferTransaction()
756+
tx.set_transaction_id(TransactionId.generate(AccountId.from_string("0.0.1234")))
757+
# Ensure client default is None
758+
mock_client.default_max_transaction_fee = None
759+
760+
tx.freeze_with(mock_client)
761+
762+
assert tx.transaction_fee == 100000000 # Default fee for TransferTransaction
763+
print(tx.__class__.__mro__)

0 commit comments

Comments
 (0)