Skip to content

Commit 45a9e31

Browse files
committed
fix: hbar cov fix
Signed-off-by: prajeeta pal <[email protected]>
1 parent aa32abd commit 45a9e31

File tree

3 files changed

+243
-16
lines changed

3 files changed

+243
-16
lines changed

CHANGELOG.md

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
66

77
## [Unreleased]
88

9-
10-
119
### Added
12-
- Added Hbar object support for TransferTransaction HBAR transfers: methods now accept Union[int, Hbar] for amount parameters, with immediate normalization to tinybars. Includes comprehensive unit tests covering various Hbar units (HBAR, MICROBAR, NANOBAR, TINYBAR) and accumulation behavior with mixed int and Hbar inputs.
10+
11+
- Added Hbar object support for TransferTransaction HBAR transfers: methods now accept Union[int, Hbar] for amount parameters, with immediate normalization to tinybars- includes comprehensive unit tests covering various Hbar units (HBAR, MICROBAR, NANOBAR, TINYBAR) and accumulation behavior with mixed int and Hbar inputs.
1312
- examples/mypy.ini for stricter type checking in example scripts
1413
- Added a GitHub Actions workflow that reminds contributors to link pull requests to issues.
1514
- Added `__str__` and `__repr__` methods to `AccountInfo` class for improved logging and debugging experience (#1098)
@@ -24,7 +23,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
2423
- Added unit tests for `SubscriptionHandle` class covering cancellation state, thread management, and join operations.
2524
- Refactored `account_create_transaction_create_with_alias.py` example by splitting monolithic function into modular functions: `generate_main_and_alias_keys()`, `create_account_with_ecdsa_alias()`, `fetch_account_info()`, `print_account_summary()` (#1016)
2625
- Added `.github/workflows/bot-pr-auto-draft-on-changes.yml` to automatically convert PRs to draft and notify authors when reviewers request changes.
27-
-
26+
-
2827
- Modularized `transfer_transaction_fungible` example by introducing `account_balance_query()` & `transfer_transaction()`.Renamed `transfer_tokens()``main()`
2928
- Phase 2 of the inactivity-unassign bot: Automatically detects stale open pull requests (no commit activity for 21+ days), comments with a helpful InactivityBot message, closes the stale PR, and unassigns the contributor from the linked issue.
3029
- Added `__str__()` to CustomFixedFee and updated examples and tests accordingly.
@@ -44,7 +43,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
4443
- Support selecting specific node account ID(s) for queries and transactions and added `Network._get_node()` with updated execution flow (#362)
4544
- Add TLS support with two-stage control (`set_transport_security()` and `set_verify_certificates()`) for encrypted connections to Hedera networks. TLS is enabled by default for hosted networks (mainnet, testnet, previewnet) and disabled for local networks (solo, localhost) (#855)
4645
- Add PR inactivity reminder bot for stale pull requests `.github/workflows/pr-inactivity-reminder-bot.yml`
47-
- Add comprehensive training documentation for _Executable class `docs/sdk_developers/training/executable.md`
46+
- Add comprehensive training documentation for \_Executable class `docs/sdk_developers/training/executable.md`
4847
- Added empty `docs/maintainers/good_first_issues.md` file for maintainers to write Good First Issue guidelines (#1034)
4948
- Added new `.github/ISSUE_TEMPLATE/04_good_first_issue_candidate.yml` file (1068)(https://github.com/hiero-ledger/hiero-sdk-python/issues/1068)
5049
- Enhanced `.github/ISSUE_TEMPLATE/01_good_first_issue.yml` with welcoming message and acceptance criteria sections to guide contributors in creating quality GFIs (#1052)
@@ -53,15 +52,16 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
5352
- Add support for include_children in TransactionGetReceiptQuery (#1100)(https://github.com/hiero-ledger/hiero-sdk-python/issues/1100)
5453
- Add new `.github/ISSUE_TEMPLATE/05_intermediate_issue.yml` file (1072)(https://github.com/hiero-ledger/hiero-sdk-python/issues/1072)
5554
- Add a workflow to notify the team when issues are labeled as “good first issues” or identified as candidates for that label: `bot-gfi-notify-team.yml`(#1115)
56-
- Added __str__ and __repr__ to AccountBalance
55+
- Added **str** and **repr** to AccountBalance
5756
- Added GitHub workflow that makes sure newly added test files follow pytest test files naming conventions (#1054)
5857
- Added advanced issue template for contributors `.github/ISSUE_TEMPLATE/06_advanced_issue.yml`.
5958
- Add new tests to `tests/unit/topic_info_query_test.py` (#1124)
6059
- Added `coding_token_transactions.md` for a high level overview training on how token transactions are created in the python sdk.
61-
- Added prompt for codeRabbit on how to review /examples ([#1180](https://github.com/hiero-ledger/hiero-sdk-python/issues/1180))
60+
- Added prompt for codeRabbit on how to review /examples ([#1180](https://github.com/hiero-ledger/hiero-sdk-python/issues/1180))
6261
- Add Linked Issue Enforcer to automatically close PRs without linked issues `.github/workflows/bot-linked-issue-enforcer.yml`.
6362

6463
### Changed
64+
6565
- Updated Codecov coverage thresholds in 'codecov.yml' to require 90% of project coverage and 92% of patch coverage (#1157)
6666
- 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)
6767
- Pylint cleanup for token_airdrop_transaction_cancel.py (#1081) [@tiya-15](https://github.com/tiya-15)
@@ -82,20 +82,17 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
8282
- Cleaned up `token_airdrop_claim_auto` example for pylint compliance (no functional changes). (#1079)
8383
- Formatted `examples/query` using black (#1082)(https://github.com/hiero-ledger/hiero-sdk-python/issues/1082)
8484
- Update team notification script and workflow for P0 issues 'p0_issues_notify_team.js'
85-
- Rename test files across the repository to ensure they consistently end with _test.py (#1055)
85+
- Rename test files across the repository to ensure they consistently end with \_test.py (#1055)
8686
- Cleaned up `token_airdrop_claim_signature_required` example for pylint compliance (no functional changes). (#1080)
87-
- Rename the file 'test_token_fee_schedule_update_transaction_e2e.py' to make it ends with _test.py as all other test files.(#1117)
87+
- Rename the file 'test_token_fee_schedule_update_transaction_e2e.py' to make it ends with \_test.py as all other test files.(#1117)
8888
- Format token examples with Black for consistent code style and improved readability (#1119)
8989
- Transformed `examples/tokens/custom_fee_fixed.py` to be an end-to-end example, that interacts with the Hedera network, rather than a static object demo.
90-
- Format token examples with Black for consistent code style and improved readability (#1119)
91-
- Replaced `ResponseCode.get_name(receipt.status)` with the `ResponseCode(receipt.status).name` across examples and integration tests for consistency. (#1136)
90+
- Format token examples with Black for consistent code style and improved readability (#1119)
91+
- Replaced `ResponseCode.get_name(receipt.status)` with the `ResponseCode(receipt.status).name` across examples and integration tests for consistency. (#1136)
9292
- Moved helpful references to Additional Context section and added clickable links.
9393
- Transformed `examples\tokens\custom_royalty_fee.py` to be an end-to-end example, that interacts with the Hedera network, rather than a static object demo.
9494
- Refactored `examples/tokens/custom_royalty_fee.py` by splitting monolithic function custom_royalty_fee_example() into modular functions create_royalty_fee_object(), create_token_with_fee(), verify_token_fee(), and main() to improve readability, cleaned up setup_client() (#1169)
9595

96-
97-
98-
9996
### Fixed
10097

10198
- Fix token association verification in `token_airdrop_transaction.py` to correctly check if tokens are associated by using `token_id in token_balances` instead of incorrectly displaying zero balances which was misleading (#[815])
@@ -107,7 +104,6 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
107104
- TLS Hostname Mismatch & Certificate Verification Failure for Nodes
108105
- Workflow does not contain permissions for `pr-check-test-files` and `pr-check-codecov`
109106

110-
111107
### Breaking Change
112108

113109
- 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`.
@@ -673,4 +669,3 @@ contract_call_local_pb2.ContractLoginfo -> contract_types_pb2.ContractLoginfo
673669
### Removed
674670

675671
- N/A
676-

tests/integration/transfer_transaction_e2e_test.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,71 @@ def test_integration_transfer_transaction_approved_token_transfer():
448448

449449
finally:
450450
env.close()
451+
452+
453+
@pytest.mark.integration
454+
def test_integration_transfer_transaction_approved_nft_transfer():
455+
"""Test NFT transfer with approval flag set to True."""
456+
env = IntegrationTestEnv()
457+
458+
try:
459+
new_account_private_key = PrivateKey.generate()
460+
new_account_public_key = new_account_private_key.public_key()
461+
462+
initial_balance = Hbar(10)
463+
464+
account_transaction = AccountCreateTransaction(
465+
key=new_account_public_key, initial_balance=initial_balance, memo="Recipient Account"
466+
)
467+
468+
receipt = account_transaction.execute(env.client)
469+
470+
assert (
471+
receipt.status == ResponseCode.SUCCESS
472+
), f"Account creation failed with status: {ResponseCode(receipt.status).name}"
473+
474+
account_id = receipt.account_id
475+
assert account_id is not None
476+
477+
token_id = create_nft_token(env)
478+
assert token_id is not None
479+
480+
mint_transaction = TokenMintTransaction(token_id=token_id, metadata=[b"test"])
481+
482+
receipt = mint_transaction.execute(env.client)
483+
484+
assert (
485+
receipt.status == ResponseCode.SUCCESS
486+
), f"NFT mint failed with status: {ResponseCode(receipt.status).name}"
487+
488+
serial_number = receipt.serial_numbers[0]
489+
490+
nft_id = NftId(token_id, serial_number)
491+
492+
associate_transaction = TokenAssociateTransaction(
493+
account_id=account_id, token_ids=[token_id]
494+
)
495+
496+
associate_transaction.freeze_with(env.client)
497+
associate_transaction.sign(new_account_private_key)
498+
receipt = associate_transaction.execute(env.client)
499+
500+
assert (
501+
receipt.status == ResponseCode.SUCCESS
502+
), f"NFT association failed with status: {ResponseCode(receipt.status).name}"
503+
504+
transfer_transaction = TransferTransaction()
505+
transfer_transaction.add_approved_nft_transfer(nft_id, env.operator_id, account_id)
506+
507+
receipt = transfer_transaction.execute(env.client)
508+
509+
assert (
510+
receipt.status == ResponseCode.SUCCESS
511+
), f"NFT transfer failed with status: {ResponseCode(receipt.status).name}"
512+
513+
query_transaction = CryptoGetAccountBalanceQuery(account_id)
514+
balance = query_transaction.execute(env.client)
515+
516+
assert balance and balance.token_balances == {token_id: serial_number}
517+
finally:
518+
env.close()

tests/unit/transfer_transaction_test.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,3 +632,167 @@ def test_invalid_amount_type_hbar_transfer(mock_account_ids):
632632

633633
with pytest.raises(TypeError, match="amount must be an int or Hbar instance"):
634634
transfer_tx.add_hbar_transfer(account_id_1, 123.45)
635+
636+
637+
def test_token_transfer_with_expected_decimals_building(mock_account_ids):
638+
"""Test token transfer with expected_decimals is properly built in transaction body."""
639+
account_id_1, account_id_2, node_account_id, token_id_1, _ = mock_account_ids
640+
transfer_tx = TransferTransaction()
641+
642+
transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_1, -100, 8)
643+
transfer_tx.add_token_transfer_with_decimals(token_id_1, account_id_2, 100, 8)
644+
transfer_tx.node_account_id = node_account_id
645+
transfer_tx.operator_account_id = account_id_1
646+
647+
result = transfer_tx.build_transaction_body()
648+
649+
assert result.HasField("cryptoTransfer")
650+
token_transfers = result.cryptoTransfer.tokenTransfers
651+
assert len(token_transfers) == 1
652+
assert token_transfers[0].token == token_id_1._to_proto()
653+
654+
token_amounts = token_transfers[0].transfers
655+
assert len(token_amounts) == 2
656+
for transfer in token_amounts:
657+
if transfer.accountID.accountNum == account_id_1.num:
658+
assert transfer.amount == -100
659+
elif transfer.accountID.accountNum == account_id_2.num:
660+
assert transfer.amount == 100
661+
662+
663+
def test_nft_transfer_with_approval_building(mock_account_ids):
664+
"""Test NFT transfers with approval flag are properly built."""
665+
account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids
666+
transfer_tx = TransferTransaction()
667+
668+
transfer_tx.add_nft_transfer(
669+
NftId(token_id_1, 1), account_id_sender, account_id_recipient, False
670+
)
671+
transfer_tx.add_nft_transfer(
672+
NftId(token_id_1, 2), account_id_sender, account_id_recipient, True
673+
)
674+
transfer_tx.node_account_id = node_account_id
675+
transfer_tx.operator_account_id = account_id_sender
676+
677+
result = transfer_tx.build_transaction_body()
678+
679+
assert result.HasField("cryptoTransfer")
680+
token_transfers = result.cryptoTransfer.tokenTransfers
681+
assert len(token_transfers) == 1
682+
683+
nft_transfers = token_transfers[0].nftTransfers
684+
assert len(nft_transfers) == 2
685+
686+
assert nft_transfers[0].serialNumber == 1
687+
assert nft_transfers[0].is_approval is False
688+
assert nft_transfers[1].serialNumber == 2
689+
assert nft_transfers[1].is_approval is True
690+
691+
692+
def test_nft_transfer_reconstruction_from_protobuf(mock_account_ids, mock_client):
693+
"""Test NFT transfer reconstruction from protobuf preserves all fields."""
694+
account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids
695+
transfer_tx = TransferTransaction()
696+
697+
transfer_tx.add_nft_transfer(
698+
NftId(token_id_1, 5), account_id_sender, account_id_recipient, True
699+
)
700+
transfer_tx.node_account_id = node_account_id
701+
transfer_tx.operator_account_id = account_id_sender
702+
703+
body = transfer_tx.build_transaction_body()
704+
body_bytes = body.SerializeToString()
705+
706+
reconstructed = TransferTransaction._from_protobuf(body, body_bytes, None)
707+
708+
assert len(reconstructed.nft_transfers[token_id_1]) == 1
709+
nft = reconstructed.nft_transfers[token_id_1][0]
710+
assert nft.token_id == token_id_1
711+
assert nft.sender_id == account_id_sender
712+
assert nft.receiver_id == account_id_recipient
713+
assert nft.serial_number == 5
714+
assert nft.is_approved is True
715+
716+
717+
def test_nft_transfers_unapproved_reconstruction(mock_account_ids):
718+
"""Test NFT transfer reconstruction with is_approved=False."""
719+
account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids
720+
transfer_tx = TransferTransaction()
721+
722+
transfer_tx.add_nft_transfer(
723+
NftId(token_id_1, 10), account_id_sender, account_id_recipient, False
724+
)
725+
transfer_tx.node_account_id = node_account_id
726+
transfer_tx.operator_account_id = account_id_sender
727+
728+
body = transfer_tx.build_transaction_body()
729+
body_bytes = body.SerializeToString()
730+
731+
reconstructed = TransferTransaction._from_protobuf(body, body_bytes, None)
732+
733+
assert len(reconstructed.nft_transfers[token_id_1]) == 1
734+
nft = reconstructed.nft_transfers[token_id_1][0]
735+
assert nft.is_approved is False
736+
737+
738+
def test_token_transfer_with_expected_decimals_reconstruction(mock_account_ids):
739+
"""Test token transfer with expected_decimals reconstruction from protobuf."""
740+
account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids
741+
transfer_tx = TransferTransaction()
742+
743+
transfer_tx.add_token_transfer_with_decimals(
744+
token_id_1, account_id_sender, -100, 6
745+
)
746+
transfer_tx.add_token_transfer_with_decimals(
747+
token_id_1, account_id_recipient, 100, 6
748+
)
749+
transfer_tx.node_account_id = node_account_id
750+
transfer_tx.operator_account_id = account_id_sender
751+
752+
body = transfer_tx.build_transaction_body()
753+
body_bytes = body.SerializeToString()
754+
755+
reconstructed = TransferTransaction._from_protobuf(body, body_bytes, None)
756+
757+
assert len(reconstructed.token_transfers[token_id_1]) == 2
758+
for token_transfer in reconstructed.token_transfers[token_id_1]:
759+
assert token_transfer.expected_decimals == 6
760+
if token_transfer.account_id == account_id_sender:
761+
assert token_transfer.amount == -100
762+
elif token_transfer.account_id == account_id_recipient:
763+
assert token_transfer.amount == 100
764+
765+
766+
def test_combined_transfers_reconstruction(mock_account_ids):
767+
"""Test reconstruction of transaction with HBAR, token, and NFT transfers."""
768+
account_id_sender, account_id_recipient, node_account_id, token_id_1, _ = mock_account_ids
769+
transfer_tx = TransferTransaction()
770+
771+
transfer_tx.add_hbar_transfer(account_id_sender, -1000)
772+
transfer_tx.add_hbar_transfer(account_id_recipient, 1000)
773+
transfer_tx.add_token_transfer_with_decimals(
774+
token_id_1, account_id_sender, -50, 8
775+
)
776+
transfer_tx.add_token_transfer_with_decimals(
777+
token_id_1, account_id_recipient, 50, 8
778+
)
779+
transfer_tx.add_nft_transfer(
780+
NftId(token_id_1, 1), account_id_sender, account_id_recipient, True
781+
)
782+
transfer_tx.node_account_id = node_account_id
783+
transfer_tx.operator_account_id = account_id_sender
784+
785+
body = transfer_tx.build_transaction_body()
786+
body_bytes = body.SerializeToString()
787+
788+
reconstructed = TransferTransaction._from_protobuf(body, body_bytes, None)
789+
790+
assert len(reconstructed.hbar_transfers) == 2
791+
assert len(reconstructed.token_transfers[token_id_1]) == 2
792+
assert len(reconstructed.nft_transfers[token_id_1]) == 1
793+
794+
nft = reconstructed.nft_transfers[token_id_1][0]
795+
assert nft.sender_id == account_id_sender
796+
assert nft.receiver_id == account_id_recipient
797+
assert nft.serial_number == 1
798+
assert nft.is_approved is True

0 commit comments

Comments
 (0)