Skip to content

Commit c25a049

Browse files
authored
Merge branch 'main' into feat/1145-linked-issue-enforce
Signed-off-by: MontyPokemon <[email protected]>
2 parents e3c1418 + 3207c68 commit c25a049

File tree

8 files changed

+250
-32
lines changed

8 files changed

+250
-32
lines changed

.coderabbit.yaml

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,51 @@ reviews:
1818
in_progress_fortune: false # Do not stall time with a message (spammy)
1919
poem: false # Do not write a literal poem (spammy)
2020
enable_prompt_for_ai_agents: false # Disable prompts for AI agents (spammy)
21+
22+
# --- CUSTOM INSTRUCTIONS FOR EXAMPLES DIRECTORY ---
23+
path_instructions:
24+
- path: "examples/**/*"
25+
instructions: |
26+
You are acting as a senior maintainer reviewing SDK examples. Your goal is to ensure examples work verbatim for users who copy-paste them.
27+
28+
**Priority 1 - Correctness**:
29+
- Verify transaction lifecycle chain (construction -> freeze_with -> sign -> execute).
30+
- Ensure `freeze_with(client)` is called BEFORE signing.
31+
- Validate that methods referenced actually exist in the `hiero_sdk_python` codebase.
32+
- Ensure response validation checks `receipt.status` against `ResponseCode` enums (e.g., `ResponseCode.SUCCESS`).
33+
34+
**Priority 2 - Transaction Lifecycle**:
35+
- Check method chaining logic.
36+
- Verify correct signing order (especially for multi-sig).
37+
- Ensure explicit `.execute(client)` calls.
38+
- Verify response property extraction (e.g., using `.token_id`, `.account_id`, `.serial_numbers`).
39+
- Ensure error handling uses `ResponseCode(receipt.status).name` for clarity.
40+
41+
**Priority 3 - Naming & Clarity**:
42+
- Enforce role-based naming: `operator_id`/`_key`, `treasury_account_id`/`_key`, `receiver_id`/`_key`.
43+
- Use `_id` suffix for AccountId and `_key` suffix for PrivateKey variables.
44+
- Validate negative examples explicitly check for failure codes (e.g., `TOKEN_HAS_NO_PAUSE_KEY`).
45+
- Ensure logical top-to-bottom flow without ambiguity.
46+
47+
**Priority 4 - Consistency**:
48+
- Verify standard patterns: `def main()`, `if __name__ == "__main__":`, `load_dotenv()`.
49+
- **IMPORT RULES**:
50+
1. Accept both top-level imports (e.g., `from hiero_sdk_python import PrivateKey`) and fully qualified imports (e.g., `from hiero_sdk_python.crypto.private_key import PrivateKey`).
51+
2. STRICTLY validate that the import path actually exists in the project structure. Compare against other files in `/examples` or your knowledge of the SDK file tree.
52+
3. Flag hallucinations immediately (e.g., `hiero_sdk_python.keys` does not exist).
53+
- Check for `try-except` blocks with `sys.exit(1)` for critical failures.
54+
55+
**Priority 5 - User Experience**:
56+
- Ensure comments explain SDK usage patterns (for users, not contributors).
57+
- Avoid nitpicking functional code.
58+
- Suggest type hints or docstrings only if they significantly improve clarity.
59+
60+
**Philosophy**:
61+
- Examples are copied by users - prioritize explicitness over brevity.
62+
- Avoid suggestions that `ruff` or linters would catch.
63+
- Be concise, technical, and opinionated.
64+
- Flag out-of-scope improvements as potential new issues rather than blocking.
65+
2166
chat:
2267
art: false # Don't draw ASCII art (false)
23-
auto_reply: false # Don't allow bot to converse (spammy)
68+
auto_reply: false # Don't allow bot to converse (spammy)

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
5656
- Added GitHub workflow that makes sure newly added test files follow pytest test files naming conventions (#1054)
5757
- Added advanced issue template for contributors `.github/ISSUE_TEMPLATE/06_advanced_issue.yml`.
5858
- Add new tests to `tests/unit/topic_info_query_test.py` (#1124)
59+
- Added `coding_token_transactions.md` for a high level overview training on how token transactions are created in the python sdk.
60+
- Added prompt for codeRabbit on how to review /examples ([#1180](https://github.com/hiero-ledger/hiero-sdk-python/issues/1180))
5961
- Add Linked Issue Enforcer to automatically close PRs without linked issues `.github/workflows/bot-linked-issue-enforcer.yml`.
6062

6163
### Changed
64+
- Updated Codecov coverage thresholds in 'codecov.yml' to require 90% of project coverage and 92% of patch coverage (#1157)
6265
- Reduce office-hours reminder spam by posting only on each user's most recent open PR, grouping by author and sorting by creation time (#1121)
6366
- Pylint cleanup for token_airdrop_transaction_cancel.py (#1081) [@tiya-15](https://github.com/tiya-15)
6467
- Move `account_allowance_delete_transaction_hbar.py` from `examples/` to `examples/account/` for better organization (#1003)
@@ -103,9 +106,10 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
103106
- TLS Hostname Mismatch & Certificate Verification Failure for Nodes
104107
- Workflow does not contain permissions for `pr-check-test-files` and `pr-check-codecov`
105108

109+
106110
### Breaking Change
107111

108-
-
112+
- Remove deprecated 'in_tinybars' parameter and update related tests `/src/hiero_sdk_python/hbar.py`, `/tests/unit/hbar_test.py` and `/src/hiero_sdk_python/tokens/custom_fixed_fee.py`.
109113

110114
## [0.1.10] - 2025-12-03
111115

codecov.yml

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# codecov.yml
22
coverage:
3-
status:
4-
project:
5-
default:
6-
target: 70%
7-
patch:
8-
default:
9-
target: 80%
10-
threshold: 1%
3+
status:
4+
project:
5+
default:
6+
target: 90%
7+
threshold: 1%
8+
patch:
9+
default:
10+
target: 92%
11+
threshold: 2%
1112

1213
comment:
13-
layout: "reach, diff, flags"
14+
layout: "reach, diff, flags"
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Coding Token Transactions
2+
3+
This guide provides a high-level overview of how token transactions are constructed in the Hiero Python SDK. It is intended for developers adding new transaction types (e.g., `TokenMintTransaction`, `TokenBurnTransaction`) to understand the architectural requirements.
4+
5+
## 1. Inheritance Structure
6+
7+
All token transactions must inherit from the base `Transaction` class.
8+
9+
```python
10+
from hiero_sdk_python.transaction.transaction import Transaction
11+
12+
class TokenAssociateTransaction(Transaction):
13+
"""
14+
Represents a token associate transaction on the Hedera network.
15+
"""
16+
17+
```
18+
19+
By inheriting from `Transaction`, your new class automatically gains essential functionality:
20+
21+
- **Signing:** `sign(private_key)`
22+
- **Freezing:** `freeze()` and `freeze_with(client)`
23+
- **Execution:** `execute(client)`
24+
- **Fee Calculation:** Handling `transaction_fee` and `max_transaction_fee`
25+
- **ID Management:** `transaction_id`, `node_account_id`
26+
27+
Your job in the subclass is to define **what** data is being sent, while the base class handles **how** it is sent.
28+
29+
## 2. Initialization and Defaults
30+
31+
In the Python SDK, we prefer a flexible initialization pattern. Constructors (`__init__`) should generally default arguments to `None`. This allows users to instantiate an empty transaction and configure it later using setters (Method Chaining).
32+
33+
### Pattern:
34+
35+
```python
36+
def __init__(
37+
self,
38+
account_id: Optional[AccountId] = None,
39+
token_ids: Optional[List[TokenId]] = None
40+
) -> None:
41+
super().__init__() # Initialize the base Transaction
42+
self.account_id = account_id
43+
self.token_ids = list(token_ids) if token_ids is not None else []
44+
45+
```
46+
47+
- **Why?** It supports both "all-in-one" instantiation and progressive building.
48+
- **Note:** Always call `super().__init__()` to initialize base fields like the signature map and transaction ID.
49+
50+
## 3. Setters and Method Chaining
51+
52+
To provide a "Pythonic" builder interface, every field should have a corresponding setter method. These methods must:
53+
54+
1. Check that the transaction is not frozen (immutable).
55+
2. Return `self` to allow chaining (e.g., `.set_foo().set_bar()`).
56+
57+
### Pattern:
58+
59+
```python
60+
def set_account_id(self, account_id: AccountId) -> "TokenAssociateTransaction":
61+
"""Sets the account ID for the token association transaction."""
62+
self._require_not_frozen() # Critical check from base class
63+
self.account_id = account_id
64+
return self
65+
66+
```
67+
68+
## 4. Protobuf Conversion
69+
70+
The Hedera network communicates via Protocol Buffers (Protobuf). Your transaction class is responsible for converting its Python fields into a Protobuf message.
71+
72+
This is typically done in three steps:
73+
74+
### Step A: Internal Helper `_build_proto_body()`
75+
76+
Create a helper method that constructs the _specific_ body for your transaction type (e.g., `TokenAssociateTransactionBody`).
77+
78+
```python
79+
def _build_proto_body(self) -> token_associate_pb2.TokenAssociateTransactionBody:
80+
if not self.account_id or not self.token_ids:
81+
raise ValueError("Account ID and token IDs must be set.")
82+
83+
return token_associate_pb2.TokenAssociateTransactionBody(
84+
account=self.account_id._to_proto(),
85+
tokens=[token_id._to_proto() for token_id in self.token_ids]
86+
)
87+
88+
```
89+
90+
### Step B: Implement `build_transaction_body()`
91+
92+
This abstract method from `Transaction` must be implemented. It packages your specific body into the main `TransactionBody`.
93+
94+
```python
95+
def build_transaction_body(self) -> transaction_pb2.TransactionBody:
96+
# 1. Build your specific proto body
97+
token_associate_body = self._build_proto_body()
98+
99+
# 2. Get the base body (contains NodeID, TxID, Fees, Memo)
100+
transaction_body = self.build_base_transaction_body()
101+
102+
# 3. Attach your specific body to the main transaction
103+
transaction_body.tokenAssociate.CopyFrom(token_associate_body)
104+
105+
return transaction_body
106+
107+
```
108+
109+
### Step C: Implement `build_scheduled_body()`
110+
111+
To support scheduled transactions, you must also implement this method. It is nearly identical to step B but uses `SchedulableTransactionBody`.
112+
113+
```python
114+
def build_scheduled_body(self) -> SchedulableTransactionBody:
115+
token_associate_body = self._build_proto_body()
116+
schedulable_body = self.build_base_scheduled_body()
117+
schedulable_body.tokenAssociate.CopyFrom(token_associate_body)
118+
return schedulable_body
119+
120+
```
121+
122+
## 5. Network Routing (`_get_method`)
123+
124+
Finally, you must tell the base class which gRPC method to call on the node. This routes the transaction to the correct service (e.g., Token Service vs. Crypto Service).
125+
126+
```python
127+
def _get_method(self, channel: _Channel) -> _Method:
128+
return _Method(
129+
transaction_func=channel.token.associateTokens, # The gRPC function
130+
query_func=None
131+
)
132+
133+
```
134+
135+
---
136+
137+
## Summary Checklist
138+
139+
When creating a new token transaction, ensure you have:
140+
141+
- [ ] **Inherited** from `Transaction`.
142+
- [ ] **Initialized** fields to `None` in `__init__`.
143+
- [ ] **Implemented Setters** that call `self._require_not_frozen()` and return `self`.
144+
- [ ] **Implemented** `_build_proto_body()` to map Python fields to Protobuf.
145+
- [ ] **Implemented** `build_transaction_body()` to merge your body into the main transaction.
146+
- [ ] **Implemented** `build_scheduled_body()` for scheduling support.
147+
- [ ] **Implemented** `_get_method()` to define the gRPC endpoint.
148+
149+
The base `Transaction` class will handle the heavy lifting of freezing, signing, serialization, and execution.
150+
151+
---

src/hiero_sdk_python/hbar.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,15 @@ class Hbar:
3030
def __init__(
3131
self,
3232
amount: Union[int, float, Decimal],
33-
unit: HbarUnit=HbarUnit.HBAR,
34-
in_tinybars: bool=False # Deperecated
33+
unit: HbarUnit=HbarUnit.HBAR
3534
) -> None:
3635
"""
3736
Create an Hbar instance with the given amount designated either in hbars or tinybars.
3837
3938
Args:
4039
amount: The numeric amount of hbar or tinybar.
4140
unit: Unit of the provided amount.
42-
in_tinybars (deprecated): If True, treat the amount as tinybars directly.
4341
"""
44-
if in_tinybars:
45-
warnings.warn(
46-
"The 'in_tinybars' parameter is deprecated and will be removed in a future release. "
47-
"Use `unit=HbarUnit.TINYBAR` instead.",
48-
DeprecationWarning
49-
)
50-
unit = HbarUnit.TINYBAR
5142

5243
if unit == HbarUnit.TINYBAR:
5344
if not isinstance(amount, int):
@@ -115,7 +106,7 @@ def from_tinybars(cls, tinybars: int) -> "Hbar":
115106
"""
116107
if not isinstance(tinybars, int):
117108
raise TypeError("tinybars must be an int.")
118-
return cls(tinybars, in_tinybars=True)
109+
return cls(tinybars, unit=HbarUnit.TINYBAR)
119110

120111
@classmethod
121112
def from_string(cls, amount: str, unit: HbarUnit = HbarUnit.HBAR) -> "Hbar":
@@ -176,4 +167,4 @@ def __ge__(self, other: object) -> bool:
176167

177168
Hbar.ZERO = Hbar(0)
178169
Hbar.MAX = Hbar(50_000_000_000)
179-
Hbar.MIN = Hbar(-50_000_000_000)
170+
Hbar.MIN = Hbar(-50_000_000_000)

src/hiero_sdk_python/tokens/custom_fixed_fee.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22
import typing
3+
import warnings
34
from hiero_sdk_python.tokens.custom_fee import CustomFee
45
from hiero_sdk_python.hbar import Hbar
56

@@ -89,6 +90,10 @@ def __str__(self) -> str:
8990
def set_amount_in_tinybars(self, amount: int) -> "CustomFixedFee":
9091
"""Sets the fee amount in tinybars.
9192
93+
.. deprecated::
94+
Use :meth:`set_hbar_amount` with :meth:`Hbar.from_tinybars` instead.
95+
For example: ``set_hbar_amount(Hbar.from_tinybars(amount))``
96+
9297
Clears any previously set denominating token ID, implying the fee is in HBAR.
9398
9499
Args:
@@ -97,6 +102,13 @@ def set_amount_in_tinybars(self, amount: int) -> "CustomFixedFee":
97102
Returns:
98103
CustomFixedFee: This CustomFixedFee instance for chaining.
99104
"""
105+
warnings.warn(
106+
"set_amount_in_tinybars() is deprecated and will be removed in a future release. "
107+
"Use set_hbar_amount(Hbar.from_tinybars(amount)) instead.",
108+
DeprecationWarning,
109+
stacklevel=2
110+
)
111+
self.denominating_token_id = None
100112
self.amount = amount
101113
return self
102114

tests/unit/custom_fee_test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from hiero_sdk_python.client.client import Client
22
import pytest
3+
import warnings
34
from unittest import mock
45
from hiero_sdk_python.tokens.custom_fee import CustomFee
56
from hiero_sdk_python.tokens.custom_fixed_fee import CustomFixedFee
@@ -9,6 +10,7 @@
910
from hiero_sdk_python.account.account_id import AccountId
1011
from hiero_sdk_python.tokens.token_id import TokenId
1112

13+
1214
pytestmark = pytest.mark.unit
1315

1416
def test_custom_fixed_fee_proto_round_trip():
@@ -253,3 +255,20 @@ def WhichOneof(self, name):
253255
return "unknown_fee"
254256
with pytest.raises(ValueError):
255257
CustomFee._from_proto(FakeProto())
258+
259+
def test_set_amount_in_tinybars_deprecation():
260+
"""Test that set_amount_in_tinybars shows deprecation warning."""
261+
fee = CustomFixedFee()
262+
263+
# Test that deprecation warning is raised
264+
with warnings.catch_warnings(record=True) as w:
265+
warnings.simplefilter("always")
266+
fee.set_amount_in_tinybars(100)
267+
268+
assert len(w) == 1
269+
assert issubclass(w[0].category, DeprecationWarning)
270+
assert "set_amount_in_tinybars() is deprecated" in str(w[0].message)
271+
272+
# Verify the method still works correctly
273+
assert fee.amount == 100
274+
assert fee.denominating_token_id is None

tests/unit/hbar_test.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,12 @@ def test_constructor():
2121
assert hbar3.to_tinybars() == 50_000_000
2222
assert hbar3.to_hbars() == 0.5
2323

24-
def test_constructor_in_tinybars():
25-
"""Test creation directly in tinybars."""
26-
hbar1 = Hbar(50, in_tinybars=True)
24+
def test_constructor_with_tinybar_unit():
25+
"""Test creation with unit set to HbarUnit.TINYBAR."""
26+
hbar1 = Hbar(50, unit=HbarUnit.TINYBAR)
2727
assert hbar1.to_tinybars() == 50
2828
assert hbar1.to_hbars() == 0.0000005
2929

30-
# If in_tinybars is False (Default)
31-
hbar2 = Hbar(50, in_tinybars=False)
32-
assert hbar2.to_tinybars() == 5_000_000_000
33-
assert hbar2.to_hbars() == 50
34-
3530
def test_constructor_with_unit():
3631
"""Test creation directly in tinybars."""
3732
hbar1 = Hbar(50, unit=HbarUnit.TINYBAR)
@@ -65,7 +60,7 @@ def test_constructor_with_unit():
6560
def test_constructor_fractional_tinybar():
6661
"""Test creation with fractional tinybars."""
6762
with pytest.raises(ValueError, match="Fractional tinybar value not allowed"):
68-
Hbar(0.1, in_tinybars=True)
63+
Hbar(0.1, unit=HbarUnit.TINYBAR)
6964

7065
def test_constructor_invalid_type():
7166
"""Test creation of Hbar with invalid type."""

0 commit comments

Comments
 (0)