Skip to content

Commit f3419a4

Browse files
authored
feat: Add Hip1313 (hiero-ledger#2308)
Signed-off-by: aceppaluni <aceppaluni@gmail.com>
1 parent 61008d7 commit f3419a4

6 files changed

Lines changed: 371 additions & 4 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Example demonstrating high-volume account creation functionality.
3+
4+
Run:
5+
uv run examples/account/high_volume_account_create_transaction.py
6+
python examples/account/high_volume_account_create_transaction.py
7+
"""
8+
9+
import sys
10+
11+
from hiero_sdk_python import (
12+
AccountCreateTransaction,
13+
AccountDeleteTransaction,
14+
Client,
15+
Hbar,
16+
PrivateKey,
17+
ResponseCode,
18+
)
19+
20+
21+
def create_account_high_volume(client):
22+
"""Create a test account using high-volume throttles."""
23+
account_private_key = PrivateKey.generate_ed25519()
24+
account_public_key = account_private_key.public_key()
25+
26+
tx = (
27+
AccountCreateTransaction()
28+
.set_key_without_alias(account_public_key)
29+
.set_initial_balance(Hbar(1))
30+
.set_account_memo("High-volume test account")
31+
.set_high_volume(True)
32+
)
33+
34+
# Set max transaction fee before freezing
35+
tx.transaction_fee = Hbar(5)
36+
37+
receipt = tx.freeze_with(client).sign(account_private_key).execute(client)
38+
39+
if receipt.status != ResponseCode.SUCCESS:
40+
print(f"Account creation failed with status: {ResponseCode(receipt.status).name}")
41+
sys.exit(1)
42+
43+
account_id = receipt.account_id
44+
print(f"\nAccount created with ID: {account_id}")
45+
46+
return account_id, account_private_key
47+
48+
49+
def main():
50+
"""
51+
Demonstrates high-volume account creation functionality by:
52+
53+
1. Setting up client with operator account
54+
2. Creating an account using high-volume throttles
55+
3. Setting a max transaction fee for dynamic pricing protection
56+
4. Deleting the created account
57+
"""
58+
client = Client.from_env()
59+
60+
# Create an account using high-volume throttles
61+
account_id, account_private_key = create_account_high_volume(client)
62+
63+
print("Account created successfully using high-volume throttles!")
64+
65+
# Delete the account
66+
receipt = (
67+
AccountDeleteTransaction()
68+
.set_account_id(account_id)
69+
.set_transfer_account_id(client.operator_account_id)
70+
.freeze_with(client)
71+
.sign(account_private_key)
72+
.execute(client)
73+
)
74+
75+
if receipt.status != ResponseCode.SUCCESS:
76+
print(f"Account delete failed with status: {ResponseCode(receipt.status).name}")
77+
sys.exit(1)
78+
79+
print("Account deleted successfully!")
80+
81+
82+
if __name__ == "__main__":
83+
main()

src/hiero_sdk_python/transaction/transaction.py

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ def __init__(self) -> None:
4747
super().__init__()
4848

4949
self.transaction_id = None
50-
self.transaction_fee: int | None = None
50+
self._transaction_fee: int | None = None
5151
self.transaction_valid_duration = 120
5252
self.generate_record = False
53+
self._high_volume = False
5354
self.memo = ""
5455
self.custom_fee_limits: list[CustomFeeLimit] = []
5556
# Maps each node's AccountId to its corresponding transaction body bytes
@@ -466,14 +467,15 @@ def build_base_transaction_body(self) -> transaction_pb2.TransactionBody:
466467
transaction_body.transactionID.CopyFrom(transaction_id_proto)
467468
transaction_body.nodeAccountID.CopyFrom(selected_node._to_proto())
468469

469-
fee = self.transaction_fee or self._default_transaction_fee
470+
fee = self._transaction_fee or self._default_transaction_fee
470471
if hasattr(fee, "to_tinybars"):
471472
transaction_body.transactionFee = int(fee.to_tinybars())
472473
else:
473474
transaction_body.transactionFee = int(fee)
474475

475476
transaction_body.transactionValidDuration.seconds = self.transaction_valid_duration
476477
transaction_body.generateRecord = self.generate_record
478+
transaction_body.high_volume = self._high_volume
477479
transaction_body.memo = self.memo
478480
custom_fee_limits = [custom_fee._to_proto() for custom_fee in self.custom_fee_limits]
479481
transaction_body.max_custom_fees.extend(custom_fee_limits)
@@ -493,7 +495,7 @@ def build_base_scheduled_body(self) -> SchedulableTransactionBody:
493495
"""
494496
schedulable_body = SchedulableTransactionBody()
495497

496-
fee = self.transaction_fee or self._default_transaction_fee
498+
fee = self._transaction_fee or self._default_transaction_fee
497499
if hasattr(fee, "to_tinybars"):
498500
schedulable_body.transactionFee = int(fee.to_tinybars())
499501
else:
@@ -604,6 +606,33 @@ def set_transaction_id(self, transaction_id: TransactionId):
604606
self.transaction_id = transaction_id
605607
return self
606608

609+
# this will preserves original behavior
610+
@property
611+
def transaction_fee(self):
612+
"""
613+
Set the maximum transaction fee for this transaction.
614+
"""
615+
return self._transaction_fee
616+
617+
@transaction_fee.setter
618+
def transaction_fee(self, fee: Hbar | int):
619+
"""
620+
Set the maximum transaction fee for this transaction.
621+
"""
622+
self._require_not_frozen()
623+
624+
if isinstance(fee, Hbar):
625+
tinybars = fee.to_tinybars()
626+
elif isinstance(fee, bool) or not isinstance(fee, int):
627+
raise TypeError("fee must be of type Hbar or int")
628+
else:
629+
tinybars = fee
630+
631+
if tinybars < 0:
632+
raise ValueError("fee must be greater than or equal to 0")
633+
634+
self._transaction_fee = tinybars
635+
607636
def to_bytes(self) -> bytes:
608637
"""
609638
Serializes the frozen transaction into its protobuf-encoded byte representation.
@@ -853,6 +882,7 @@ def _from_protobuf(cls, transaction_body, body_bytes: bytes, sig_map):
853882
transaction.transaction_fee = transaction_body.transactionFee
854883
transaction.transaction_valid_duration = transaction_body.transactionValidDuration.seconds
855884
transaction.generate_record = transaction_body.generateRecord
885+
transaction._high_volume = transaction_body.high_volume
856886
transaction.memo = transaction_body.memo
857887

858888
if transaction_body.max_custom_fees:
@@ -886,6 +916,31 @@ def set_batch_key(self, key: Key):
886916
self.batch_key = key
887917
return self
888918

919+
def set_high_volume(self, high_volume: bool) -> Transaction:
920+
"""
921+
Enables or disables high-volume throttles for this transaction.
922+
923+
When enabled, the transaction may use high-volume capacity with
924+
dynamic pricing as defined by HIP-1313.
925+
926+
Args:
927+
high_volume (bool): Whether to enable high-volume throttles.
928+
929+
Returns:
930+
Transaction: The current transaction instance for method chaining.
931+
932+
Raises:
933+
TypeError: If high_volume is not a bool.
934+
Exception: If the transaction has already been frozen.
935+
"""
936+
self._require_not_frozen()
937+
938+
if not isinstance(high_volume, bool):
939+
raise TypeError("high_volume must be of type bool")
940+
941+
self._high_volume = high_volume
942+
return self
943+
889944
def batchify(self, client: Client, batch_key: Key):
890945
"""
891946
Marks the current transaction as an inner (batched) transaction.
@@ -935,3 +990,13 @@ def body_size(self) -> int:
935990
"""Returns just the transaction body size in bytes after encoding"""
936991
self._require_frozen()
937992
return self.build_transaction_body().ByteSize()
993+
994+
@property
995+
def high_volume(self) -> bool:
996+
"""
997+
Returns whether high-volume throttles are enabled for this transaction.
998+
999+
Returns:
1000+
bool: True if high-volume throttles are enabled.
1001+
"""
1002+
return self._high_volume

src/hiero_sdk_python/transaction/transaction_record.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class TransactionRecord:
8989
transaction_hash: bytes | None = None
9090
transaction_memo: str | None = None
9191
transaction_fee: int | None = None
92+
high_volume_pricing_multiplier: int | None = None
9293
receipt: TransactionReceipt | None = None
9394
call_result: ContractFunctionResult | None = None
9495

@@ -144,6 +145,7 @@ def __repr__(self) -> str:
144145
f"transaction_hash={self.transaction_hash}, "
145146
f"transaction_memo='{self.transaction_memo}', "
146147
f"transaction_fee={self.transaction_fee}, "
148+
f"high_volume_pricing_multiplier={self.high_volume_pricing_multiplier}, "
147149
f"receipt_status='{status}', "
148150
f"token_transfers={dict(self.token_transfers)}, "
149151
f"nft_transfers={dict(self.nft_transfers)}, "
@@ -240,6 +242,7 @@ def _from_proto(
240242
if proto.HasField("contractCreateResult")
241243
else None
242244
)
245+
243246
alias = proto.alias if proto.alias else None
244247
ethereum_hash = proto.ethereum_hash if proto.ethereum_hash else None
245248
evm_address = proto.evm_address if proto.evm_address else None
@@ -279,6 +282,7 @@ def _from_proto(
279282
paid_staking_rewards=paid_staking_rewards,
280283
evm_address=evm_address,
281284
contract_create_result=contract_create_result,
285+
high_volume_pricing_multiplier=proto.high_volume_pricing_multiplier,
282286
)
283287

284288
@staticmethod
@@ -459,4 +463,7 @@ def _to_proto(self) -> transaction_record_pb2.TransactionRecord:
459463
if self.contract_create_result is not None:
460464
record_proto.contractCreateResult.CopyFrom(self.contract_create_result._to_proto())
461465

466+
if self.high_volume_pricing_multiplier is not None:
467+
record_proto.high_volume_pricing_multiplier = self.high_volume_pricing_multiplier
468+
462469
return record_proto

tests/integration/account_create_transaction_e2e_test.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from hiero_sdk_python.exceptions import PrecheckError
88
from hiero_sdk_python.hbar import Hbar
99
from hiero_sdk_python.query.account_info_query import AccountInfoQuery
10+
from hiero_sdk_python.query.transaction_record_query import TransactionRecordQuery
1011
from hiero_sdk_python.response_code import ResponseCode
1112

1213

@@ -355,3 +356,71 @@ def test_create_account_with_negative_initial_balance(env):
355356
tx.execute(env.client)
356357

357358
assert e.value.status == ResponseCode.INVALID_INITIAL_BALANCE
359+
360+
361+
def test_can_create_account_with_high_volume(env):
362+
"""
363+
Test creation of an account with high-volume pricing enabled and verify
364+
the resulting pricing multiplier and account properties.
365+
"""
366+
key = PrivateKey.generate_ed25519()
367+
368+
tx = AccountCreateTransaction().set_key_without_alias(key).set_initial_balance(Hbar(1)).set_high_volume(True)
369+
370+
tx.transaction_fee = Hbar(10)
371+
tx.freeze_with(env.client)
372+
373+
receipt = tx.execute(env.client)
374+
375+
assert receipt.status == ResponseCode.SUCCESS, f"Unexpected status: {ResponseCode(receipt.status).name}"
376+
377+
account_id = receipt.account_id
378+
assert account_id is not None
379+
380+
record = TransactionRecordQuery(tx.transaction_id).execute(env.client)
381+
382+
assert record.high_volume_pricing_multiplier >= 1000
383+
384+
info = AccountInfoQuery().set_account_id(account_id).execute(env.client)
385+
386+
assert info.account_id == account_id
387+
assert info.is_deleted is False
388+
assert str(info.key) == str(key.public_key())
389+
assert info.balance == Hbar(1)
390+
391+
392+
def test_can_create_account_with_high_volume_and_valid_max_fee(env):
393+
"""
394+
Verify that a high-volume account can be created when the transaction
395+
maximum fee is set high enough to cover the increased cost.
396+
"""
397+
key = PrivateKey.generate_ed25519()
398+
399+
tx = AccountCreateTransaction().set_key_without_alias(key).set_initial_balance(Hbar(1)).set_high_volume(True)
400+
401+
tx.transaction_fee = Hbar(10)
402+
403+
receipt = tx.execute(env.client)
404+
account_id = receipt.account_id
405+
406+
assert receipt.status == ResponseCode.SUCCESS
407+
assert account_id is not None
408+
409+
info = AccountInfoQuery().set_account_id(account_id).execute(env.client)
410+
411+
assert info.account_id == account_id
412+
413+
414+
def test_create_account_with_high_volume_fails_with_insufficient_tx_fee(env):
415+
"""
416+
Verify that an account creation transaction with high-volume pricing
417+
fails with INSUFFICIENT_TX_FEE when the transaction fee is too low.
418+
"""
419+
key = PrivateKey.generate_ed25519()
420+
421+
tx = AccountCreateTransaction().set_key_without_alias(key).set_initial_balance(Hbar(1)).set_high_volume(True)
422+
423+
tx.transaction_fee = Hbar(1)
424+
receipt = tx.execute(env.client)
425+
426+
assert receipt.status == ResponseCode.INSUFFICIENT_TX_FEE

0 commit comments

Comments
 (0)